T2:最大值与最小值
众所周知,小葱同学擅长计算,尤其组合数但这个题和组合数什么关系。
给定一张有向图,每个点有点权。试找到一条路径,使得该路径上的点权最大值减去点权最小值最大,问这个差最大是多少。
缩点后在DAG上DP,对每个dcc维护四个信息preMin/preMax/nxtMin/nxtMax,分别表示所有前驱、后继中的最大、最小点权,都要把这个dcc本身算在内。分别使用拓扑排序、记忆化搜索来更新前驱和后继信息,目标状态即为max{max(preMax[u] - nxtMin[u], nxtMax[u] - preMin[u])},可以保证组合得到最优路径上的差值。
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <queue>
- #include <vector>
- #include <cctype>
- #define maxn 100010
- #define maxm 500010
- using namespace std;
- void open_file(string s) {
- string In = s + ".in", Out = s + ".out";
- freopen(In.c_str(), "r", stdin);
- freopen(Out.c_str(), "w", stdout);
- }
- template <class T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-') f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- }
- int n, m;
- int w[maxn], c[maxn], maxx[maxn], minn[maxn];
- struct E {
- int to, nxt;
- } edge[maxm << 1];
- int head[maxn], head2[maxn], top;
- inline void insert(int* a, int u, int v) {
- edge[++top] = (E) {v, a[u]};
- a[u] = top;
- }
- int dfn[maxn], low[maxn], tmr, stp, sta[maxn], cnt;
- bool ins[maxn];
- void tarjan(int u) {
- dfn[u] = low[u] = ++tmr;
- sta[++stp] = u, ins[u] = true;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (!dfn[v]) {
- tarjan(v);
- low[u] = min(low[u], low[v]);
- } else if (ins[v])
- low[u] = min(low[u], dfn[v]);
- }
- if (dfn[u] == low[u]) {
- ++cnt;
- int x;
- // printf("# %d: ", cnt);
- do {
- x = sta[stp--];
- // printf("%d ", x);
- ins[x] = false;
- c[x] = cnt;
- maxx[cnt] = max(maxx[cnt], w[x]);
- minn[cnt] = min(minn[cnt], w[x]);
- } while (x != u);
- // puts("");
- }
- }
- int ind[maxn];
- void rebuild() {
- for (int u = 1; u <= n; ++u)
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (c[u] == c[v]) continue;
- insert(head2, c[u], c[v]), ++ind[c[v]];
- }
- }
- int ans = -0x3f3f3f3f;
- int preMax[maxn], preMin[maxn], nxtMax[maxn], nxtMin[maxn];
- bool vis[maxn];
- vector<int> st;
- void dfs(int u) { //ºó¼Ì
- if (vis[u]) return;
- vis[u] = true;
- nxtMax[u] = maxx[u], nxtMin[u] = minn[u];
- for (int i = head2[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- dfs(v);
- nxtMax[u] = max(nxtMax[u], nxtMax[v]);
- nxtMin[u] = min(nxtMin[u], nxtMin[v]);
- }
- }
- void dp() { //Ç°Çý
- queue<int> que;
- for (int i = 1; i <= cnt; ++i) {
- preMax[i] = maxx[i], preMin[i] = minn[i];
- if (!ind[i]) que.push(i), st.push_back(i);
- }
- while (que.size()) {
- int u = que.front(); que.pop();
- for (int i = head2[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- preMax[v] = max(preMax[v], preMax[u]);
- preMin[v]= min(preMin[v], preMin[u]);
- if (!(--ind[v])) que.push(v);
- }
- }
- return;
- }
- void init() {
- memset(maxx, -0x3f, sizeof(maxx));
- memset(minn, 0x3f, sizeof(minn));
- memset(preMax, -0x3f, sizeof(preMax));
- memset(preMin, 0x3f, sizeof(preMin));
- memset(nxtMax, -0x3f, sizeof(nxtMax));
- memset(nxtMin, 0x3f, sizeof(nxtMin));
- }
- int main() {
- open_file("b");
- init();
- read(n), read(m);
- for (int i = 1; i <= n; ++i)
- read(w[i]);
- int u, v;
- for (int i = 1; i <= m; ++i) {
- read(u), read(v);
- insert(head, u, v);
- }
- for (int i = 1; i <= n; ++i)
- if (!dfn[i]) tarjan(i);
- rebuild();
- dp();
- for (int i = 0; i < st.size(); ++i)
- dfs(st[i]);
- for (int i = 1; i <= n; ++i)
- ans = max(ans, max(nxtMax[i] - preMin[i], preMax[i] - nxtMin[i]));
- printf("%d\n", ans);
- return 0;
- }
T3:两把刷子
众所周知,小葱同学擅长计算,尤其擅长组合数,但这个题和组合数没什么关系。
有N个人 ,每都有两把刷子,每个刷子都有一个属性值。如果说一个人拿着的两把刷子的属性值之差的绝对值超过了c,则这个人无法使用他的两把刷 。现在你可以选择交换不同人的某把刷子, 使得每个人都能够使用他们的刷子, 问最小所需要的交换次数。
状压dp,状态k的各位表示这个人是否在当前集合中。定义把集合k分成若干个不重不漏的子集称为集合k的一个独立子集划分,独立集满足其内部本身可以通过交换成为合法的状态。找规律得一个大小为m的集合的最少交换次数cnt = m - f[k],其中f[k]表示k最大的独立子集划分所包含的子集数。显然,若一个集合本身可以经过交换成为合法,则自身至少能够作为一个独立集,f初值设为1,否则为-inf。(f[0] = 0)
转移方程f[k] = max(f[i] + f[k ^ i]),其中i是k的子集,i^k是对应的补集。由于不容易确定转移顺序,使用记忆化搜索进行转移。对每一个状态sort后暴力两两作差判断是否能够成为独立集。
- #include <iostream>
- #include <cstdio>
- #include <vector>
- #include <cmath>
- #include <algorithm>
- const int inf = 0x3f3f3f3f;
- using namespace std;
- int a[2][17], n, c;
- int f[1 << 17];
- vector<int> tmp;
- int check(int k) {
- tmp.clear();
- for (int i = 0; i < n; ++i)
- if ((1 << i) & k) tmp.push_back(a[0][i + 1]), tmp.push_back(a[1][i + 1]);
- sort(tmp.begin(), tmp.end());
- for (int i = 0; i < tmp.size(); i += 2)
- if (abs(tmp[i] - tmp[i + 1]) > c)
- return -inf;
- return 1;
- }
- void dfs(int k) {
- if (f[k] || !k) return;
- f[k] = check(k);
- for (int i = k; i; i = k & (i - 1)) {
- dfs(i); dfs(k ^ i);
- f[k] = max(f[k], f[i] + f[k^i]);
- }
- return;
- }
- int main() {
- freopen("c.in", "r", stdin);
- freopen("c.out", "w", stdout);
- cin >> n >> c;
- for (int i = 1; i <= n; ++i)
- cin >> a[0][i] >> a[1][i];
- dfs((1 << n) - 1);
- printf("%d", n - f[(1 << n) - 1]);
- return 0;
- }
T4:fib数列
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
给定 N个数,M次操作 ,操作有如下两种 :
-
给定 l,r,询问第l个数到第r个数的和。
-
给定 l,r,x,将这段区间的第i个数加上f(i+x),其中f(i+x)代表的是斐波那契 数列的第 i + x + 1项 。 对于斐波那契数列我们有 f(0)=0,f(1)=1,f(n)=f(n-1)+f(n-2)。
考虑使用线段树,需要解决的有如下两个问题:
1、如何设置标记tag,使其符合可加性并且能够由之快速推出当前区间的累加值?
2、如何下推这个标记?
如果我们记录每个区间所累计的fib数的前两项,显然由这两个值和数列长度可以推出整个区间所加的数列。并且这个tag是可以累加的,证明略。那么问题就集中在了如何用两个任意的数作为首项,快速地求出对应“广义斐波那契数列”的前n项和。
设f表示经典斐波那契数列,f[0] = 0, f[1] = 1, 数列S表示其前缀和;定义广义斐波那契数列F,F[0] = a,F[1] = b,F[i] = F[i - 1] + F[i - 2](i >= 2);令S’表示其前缀和。
F、S'满足如下通项式:
F[n] = f[n-1] * a + f[n] * b
S'[n] = S[n-1] * a + S[n] * b = (f[n + 1] - 1)a + (f[n + 2] - 1)b
利用这两个式子,我们可以O(1)求出某个区间的累计加和与它右儿子的两个递推首项,那么施加标记和下推标记就解决了。以下的代码中tag维护的是当前区间累计的最左端项b和它前面的一项a,原理相同。有些难调……
给出的正解为用矩阵快速幂来推广义fib数列的通项,常数较大,但是相应地不需要记这个结论。但是你要会写矩阵快速幂
- #include <iostream>
- #include <cstdio>
- #include <vector>
- #include <cmath>
- #include <ctime>
- #define BUG puts("$$$")
- #define A first
- #define B second
- #define mp make_pair
- #define LL long long
- using namespace std;
- const LL mod = 1e9 + 7; const int maxn = 1e5 + 10;
- using namespace std;
- pair<LL, LL> operator + (pair<LL, LL> a, pair<LL, LL> b) {
- return mp((a.A + b.A) % mod, (a.B + b.B) % mod);
- }
- void open_file(string s) {
- string In = s + ".in", Out = s + ".out";
- freopen(In.c_str(), "r", stdin);
- freopen(Out.c_str(), "w", stdout);
- }
- template <class T>
- void read(T& x) {
- x = 0;
- T f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- }
- int n, m;
- LL fib[maxn << 1];
- void init() {
- fib[0] = 0, fib[1] = 1;
- for (register int i = 2; i < (maxn << 1); ++i) {
- fib[i] = (fib[i - 1] + fib[i - 2]) % mod;
- }
- }
- inline LL get_Sn(pair<LL, LL> op, LL N) { //广义Sn
- return (((op.A * (fib[N + 1] - 1)) + (op.B * (fib[N + 2] - 1)))) % mod;
- }
- inline LL get_F(pair<LL, LL> op, LL N) { //广义fib
- if (N == 0)
- return op.A;
- return ((op.A * fib[N - 1]) % mod + (op.B * fib[N])) % mod;
- }
- LL a[maxn];
- namespace Segment_tree {
- #define lc (nd << 1)
- #define rc ((nd << 1) | 1)
- #define mid ((l + r) >> 1)
- struct node {
- LL dat; int len;
- inline friend node operator + (node a, node b) {
- return (node){(a.dat + b.dat) % mod, a.len + b.len};
- }
- } seg[maxn << 2];
- pair<LL, LL> tag[maxn << 2];
- inline void update(int nd) {
- seg[nd] = seg[lc] + seg[rc];
- }
- inline void put_tag(int nd, pair<LL, LL> op) {
- seg[nd].dat = (seg[nd].dat + get_Sn(op, seg[nd].len)) % mod;
- tag[nd] = tag[nd] + op;
- }
- inline void push_down(int nd) {
- put_tag(lc, tag[nd]);
- put_tag(rc, mp(get_F(tag[nd], seg[lc].len), get_F(tag[nd], seg[lc].len + 1))); //右儿子的tag需要通项公式得出
- tag[nd] = mp(0, 0);
- }
- void build(int nd, int l, int r) {
- if (l == r) {
- seg[nd] = (node) {a[l] % mod, 1};
- return;
- }
- build(lc, l, mid);
- build(rc, mid + 1, r);
- update(nd);
- }
- void modify(int nd, int l, int r, const int& ql, const int& qr, const int& id) {
- if (l >= ql && r <= qr) {
- LL x = l - ql;
- put_tag(nd, mp(fib[x + id], fib[x + id + 1]));
- return;
- } else if (l > qr || r < ql)
- return;
- push_down(nd);
- modify(lc, l, mid, ql, qr, id);
- modify(rc, mid + 1, r, ql, qr, id);
- update(nd);
- }
- LL query(int nd, int l, int r, const int& ql, const int& qr) {
- if (l >= ql && r <= qr) {
- return seg[nd].dat;
- } else if (l > qr || r < ql)
- return 0;
- push_down(nd);
- return (query(lc, l, mid, ql, qr) + query(rc, mid + 1, r, ql, qr)) % mod;
- }
- } using namespace Segment_tree;
- int main() {
- open_file("d");
- read(n), read(m);
- for (register int i = 1; i <= n; ++i)
- read(a[i]);
- init();
- build(1, 1, n);
- int l, r, opt, x;
- for (register int i = 1; i <= m; ++i) {
- read(opt), read(l), read(r);
- if (opt == 1) {
- printf("%lld\n", query(1, 1, n, l, r));
- } else {
- read(x);
- modify(1, 1, n, l, r, x - 1);
- }
- }
- return 0;
- }