【2019.8.23】测试题目订正

 ̄綄美尐妖づ 提交于 2019-11-28 11:14:37

T2:最大值与最小值

众所周知,小葱同学擅长计算,尤其组合数但这个题和组合数什么关系。

给定一张有向图,每个点有点权。试找到一条路径,使得该路径上的点权最大值减去点权最小值最大,问这个差最大是多少。

 

   缩点后在DAG上DP,对每个dcc维护四个信息preMin/preMax/nxtMin/nxtMax,分别表示所有前驱、后继中的最大、最小点权,都要把这个dcc本身算在内。分别使用拓扑排序、记忆化搜索来更新前驱和后继信息,目标状态即为max{max(preMax[u] - nxtMin[u], nxtMax[u] - preMin[u])},可以保证组合得到最优路径上的差值。

 

  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4. #include <queue>  
  5. #include <vector>  
  6. #include <cctype>  
  7. #define maxn 100010  
  8. #define maxm 500010  
  9. using namespace std;  
  10. void open_file(string s) {  
  11.     string In = s + ".in", Out = s + ".out";  
  12.     freopen(In.c_str(), "r", stdin);  
  13.     freopen(Out.c_str(), "w", stdout);  
  14. }  
  15. template <class T>  
  16. void read(T &x) {  
  17.     x = 0;  
  18.     int f = 1;  
  19.     char ch = getchar();  
  20.     while (!isdigit(ch)) {  
  21.         if (ch == '-') f = -1;  
  22.         ch = getchar();  
  23.     }  
  24.     while (isdigit(ch)) {  
  25.         x = x * 10 + (ch ^ 48);  
  26.         ch = getchar();  
  27.     }  
  28.     x *= f;  
  29. }  
  30. int n, m;  
  31. int w[maxn], c[maxn], maxx[maxn], minn[maxn];  
  32. struct E {  
  33.     int to, nxt;  
  34. } edge[maxm << 1];  
  35. int head[maxn], head2[maxn], top;  
  36. inline void insert(int* a, int u, int v) {  
  37.     edge[++top] = (E) {v, a[u]};  
  38.     a[u] = top;  
  39. }  
  40. int dfn[maxn], low[maxn], tmr, stp, sta[maxn], cnt;  
  41. bool ins[maxn];  
  42. void tarjan(int u) {  
  43.     dfn[u] = low[u] = ++tmr;  
  44.     sta[++stp] = u, ins[u] = true;  
  45.     for (int i = head[u]; i; i = edge[i].nxt) {  
  46.         int v = edge[i].to;  
  47.         if (!dfn[v]) {  
  48.             tarjan(v);  
  49.             low[u] = min(low[u], low[v]);  
  50.         } else if (ins[v])  
  51.             low[u] = min(low[u], dfn[v]);  
  52.     }  
  53.     if (dfn[u] == low[u]) {  
  54.         ++cnt;  
  55.         int x;  
  56. //      printf("# %d: ", cnt);  
  57.         do {  
  58.             x = sta[stp--];  
  59. //          printf("%d ", x);  
  60.             ins[x] = false;  
  61.             c[x] = cnt;  
  62.             maxx[cnt] = max(maxx[cnt], w[x]);  
  63.             minn[cnt] = min(minn[cnt], w[x]);  
  64.         } while (x != u);  
  65. //      puts("");  
  66.     }  
  67. }  
  68. int ind[maxn];  
  69. void rebuild() {  
  70.     for (int u = 1; u <= n; ++u)  
  71.         for (int i = head[u]; i; i = edge[i].nxt) {  
  72.             int v = edge[i].to;  
  73.             if (c[u] == c[v]) continue;  
  74.             insert(head2, c[u], c[v]), ++ind[c[v]];  
  75.         }  
  76. }  
  77. int ans = -0x3f3f3f3f;  
  78. int preMax[maxn], preMin[maxn], nxtMax[maxn], nxtMin[maxn];  
  79. bool vis[maxn];  
  80. vector<int> st;  
  81. void dfs(int u) { //ºó¼Ì   
  82.     if (vis[u]) return;  
  83.     vis[u] = true;  
  84.     nxtMax[u] = maxx[u], nxtMin[u] = minn[u];  
  85.     for (int i = head2[u]; i; i = edge[i].nxt) {  
  86.         int v = edge[i].to;  
  87.         dfs(v);  
  88.         nxtMax[u] = max(nxtMax[u], nxtMax[v]);  
  89.         nxtMin[u] = min(nxtMin[u], nxtMin[v]);  
  90.     }  
  91. }  
  92. void dp() { //Ç°Çý   
  93.     queue<int> que;  
  94.     for (int i = 1; i <= cnt; ++i) {  
  95.         preMax[i] = maxx[i], preMin[i] = minn[i];  
  96.         if (!ind[i]) que.push(i), st.push_back(i);  
  97.     }  
  98.     while (que.size()) {  
  99.         int u = que.front(); que.pop();  
  100.         for (int i = head2[u]; i; i = edge[i].nxt) {  
  101.             int v = edge[i].to;  
  102.             preMax[v] = max(preMax[v], preMax[u]);  
  103.             preMin[v]=  min(preMin[v], preMin[u]);  
  104.             if (!(--ind[v])) que.push(v);  
  105.         }  
  106.     }  
  107.     return;  
  108. }  
  109. void init() {  
  110.     memset(maxx, -0x3f, sizeof(maxx));  
  111.     memset(minn, 0x3f, sizeof(minn));  
  112.     memset(preMax, -0x3f, sizeof(preMax));  
  113.     memset(preMin, 0x3f, sizeof(preMin));  
  114.     memset(nxtMax, -0x3f, sizeof(nxtMax));  
  115.     memset(nxtMin, 0x3f, sizeof(nxtMin));  
  116. }  
  117. int main() {  
  118.     open_file("b");  
  119.     init();  
  120.     read(n), read(m);  
  121.     for (int i = 1; i <= n; ++i)  
  122.         read(w[i]);  
  123.     int u, v;  
  124.     for (int i = 1; i <= m; ++i) {  
  125.         read(u), read(v);  
  126.         insert(head, u, v);  
  127.     }  
  128.       
  129.     for (int i = 1; i <= n; ++i)   
  130.         if (!dfn[i]) tarjan(i);  
  131.     rebuild();  
  132.     dp();  
  133.     for (int i = 0; i < st.size(); ++i)  
  134.         dfs(st[i]);  
  135.     for (int i = 1; i <= n; ++i)  
  136.         ans = max(ans, max(nxtMax[i] - preMin[i], preMax[i] - nxtMin[i]));  
  137.     printf("%d\n", ans);  
  138.     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后暴力两两作差判断是否能够成为独立集。

  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <vector>  
  4. #include <cmath>  
  5. #include <algorithm>  
  6. const int inf = 0x3f3f3f3f;  
  7. using namespace std;  
  8. int a[2][17], n, c;  
  9. int f[1 << 17];  
  10. vector<int> tmp;  
  11. int check(int k) {  
  12.     tmp.clear();  
  13.     for (int i = 0; i < n; ++i)  
  14.         if ((1 << i) & k) tmp.push_back(a[0][i + 1]), tmp.push_back(a[1][i + 1]);  
  15.     sort(tmp.begin(), tmp.end());  
  16.     for (int i = 0; i < tmp.size(); i += 2)  
  17.         if (abs(tmp[i] - tmp[i + 1]) > c)      
  18.             return -inf;  
  19.     return 1;  
  20. }  
  21. void dfs(int k) {  
  22.     if (f[k] || !k) return;  
  23.     f[k] = check(k);  
  24.     for (int i = k; i; i = k & (i - 1)) {  
  25.         dfs(i); dfs(k ^ i);  
  26.         f[k] = max(f[k], f[i] + f[k^i]);  
  27.     }  
  28.     return;  
  29. }  
  30. int main() {  
  31.     freopen("c.in", "r", stdin);  
  32.     freopen("c.out", "w", stdout);  
  33.     cin >> n >> c;  
  34.     for (int i = 1; i <= n; ++i)  
  35.         cin >> a[0][i] >> a[1][i];  
  36.     dfs((1 << n) - 1);  
  37.     printf("%d", n - f[(1 << n) - 1]);  
  38.     return 0;  
  39. }  

T4:fib数列

众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。

给定 N个数,M次操作 ,操作有如下两种 :

  1. 给定 l,r,询问第l个数到第r个数的和。

  2. 给定 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数列的通项,常数较大,但是相应地不需要记这个结论。但是你要会写矩阵快速幂

  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <vector>  
  4. #include <cmath>  
  5. #include <ctime>  
  6. #define BUG puts("$$$")  
  7. #define A first  
  8. #define B second  
  9. #define mp make_pair  
  10. #define LL long long  
  11. using namespace std;  
  12. const LL mod = 1e9 + 7; const int maxn = 1e5 + 10;  
  13. using namespace std;  
  14. pair<LL, LL> operator + (pair<LL, LL> a, pair<LL, LL> b) {   
  15.     return mp((a.A + b.A) % mod, (a.B + b.B) % mod);  
  16. }  
  17. void open_file(string s) {  
  18.     string In = s + ".in", Out = s + ".out";  
  19.     freopen(In.c_str(), "r", stdin);  
  20.     freopen(Out.c_str(), "w", stdout);  
  21. }  
  22. template <class T>  
  23. void read(T& x) {  
  24.     x = 0;  
  25.     T f = 1;  
  26.     char ch = getchar();  
  27.     while (!isdigit(ch)) {  
  28.         if (ch == '-')  
  29.             f = -1;  
  30.         ch = getchar();  
  31.     }  
  32.     while (isdigit(ch)) {  
  33.         x = x * 10 + (ch ^ 48);  
  34.         ch = getchar();  
  35.     }  
  36.     x *= f;  
  37. }  
  38. int n, m;  
  39. LL fib[maxn << 1];  
  40. void init() {  
  41.     fib[0] = 0, fib[1] = 1;  
  42.     for (register int i = 2; i < (maxn << 1); ++i) {  
  43.         fib[i] = (fib[i - 1] + fib[i - 2]) % mod;  
  44.     }  
  45. }  
  46. inline LL get_Sn(pair<LL, LL> op, LL N) {  //广义Sn
  47.     return (((op.A * (fib[N + 1] - 1)) + (op.B * (fib[N + 2] - 1)))) % mod;  
  48. }  
  49. inline LL get_F(pair<LL, LL> op, LL N) {  //广义fib
  50.     if (N == 0)  
  51.         return op.A;  
  52.     return ((op.A * fib[N - 1]) % mod + (op.B * fib[N])) % mod;  
  53. }  
  54. LL a[maxn];  
  55. namespace Segment_tree {  
  56.     #define lc (nd << 1)  
  57.     #define rc ((nd << 1) | 1)  
  58.     #define mid ((l + r) >> 1)  
  59.     struct node {  
  60.         LL dat; int len;  
  61.         inline friend node operator + (node a, node b) {  
  62.             return (node){(a.dat + b.dat) % mod, a.len + b.len};  
  63.         }  
  64.     } seg[maxn << 2];  
  65.     pair<LL, LL> tag[maxn << 2];  
  66.     inline void update(int nd) {  
  67.         seg[nd] = seg[lc] + seg[rc];  
  68.     }  
  69.     inline void put_tag(int nd, pair<LL, LL> op) {  
  70.         seg[nd].dat = (seg[nd].dat + get_Sn(op, seg[nd].len)) % mod;  
  71.         tag[nd] = tag[nd] + op;  
  72.     }  
  73.     inline void push_down(int nd) {  
  74.         put_tag(lc, tag[nd]);  
  75.         put_tag(rc, mp(get_F(tag[nd], seg[lc].len), get_F(tag[nd], seg[lc].len + 1)));  //右儿子的tag需要通项公式得出
  76.         tag[nd] = mp(0, 0);  
  77.     }  
  78.     void build(int nd, int l, int r) {  
  79.         if (l == r) {  
  80.             seg[nd] = (node) {a[l] % mod, 1};  
  81.             return;  
  82.         }  
  83.         build(lc, l, mid);  
  84.         build(rc, mid + 1, r);  
  85.         update(nd);  
  86.     }  
  87.     void modify(int nd, int l, int r, const int& ql, const int& qr, const int& id) {  
  88.         if (l >= ql && r <= qr) {  
  89.             LL x = l - ql;
  90.             put_tag(nd, mp(fib[x + id], fib[x + id + 1]));  
  91.             return;  
  92.         } else if (l > qr || r < ql)  
  93.             return;  
  94.         push_down(nd);  
  95.         modify(lc, l, mid, ql, qr, id);  
  96.         modify(rc, mid + 1, r, ql, qr, id);  
  97.         update(nd);  
  98.     }  
  99.     LL query(int nd, int l, int r, const int& ql, const int& qr) {  
  100.         if (l >= ql && r <= qr) {  
  101.             return seg[nd].dat;  
  102.         } else if (l > qr || r < ql)  
  103.             return 0;  
  104.         push_down(nd);  
  105.         return (query(lc, l, mid, ql, qr) + query(rc, mid + 1, r, ql, qr)) % mod;  
  106.     }  
  107. using namespace Segment_tree;  
  108. int main() {  
  109.     open_file("d");  
  110.     read(n), read(m);  
  111.     for (register int i = 1; i <= n; ++i)  
  112.         read(a[i]);  
  113.     init();  
  114.     build(1, 1, n);  
  115.     int l, r, opt, x;  
  116.     for (register int i = 1; i <= m; ++i) {  
  117.         read(opt), read(l), read(r);  
  118.         if (opt == 1) {  
  119.             printf("%lld\n", query(1, 1, n, l, r));  
  120.         } else {  
  121.             read(x);  
  122.             modify(1, 1, n, l, r, x - 1);  
  123.         }  
  124.     }  
  125.     return 0;  
  126. }  
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!