训练!!!!
首先要猜测一下策略,策略是将n个字符均分,每敲固定的字符就保存一下。
比如将n分成i段,那么每一段的长度就是k或k+1,余数部分就分配到每个k,变成k+1
那么这样就要处理没有保存的情况下,打k个字符的期望
用dp[k]表示打k个字符的期望,注意这时不是逆推,因为需要的就是正推的。
那么有dp[i] = dp[i - 1] + p(1 + dp[i]) + (1 - p) 可化为dp[i] = (dp[i - 1] + 1) / (1 - p)
输出时6位小数即可,我输出了10位反而wa了
- #include<bits/stdc++.h>
- #define l(k) (k << 1)
- #define r(k) (k << 1 | 1)
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e5 + 10;
- double dp[N], p;
- int n, x;
-
- int main()
- {
- int T, kase = 0;
- scanf("%d", &T);
- while(T--)
- {
- scanf("%d%lf%d", &n, &p, &x);
- _for(i, 1, n) dp[i] = (dp[i - 1] + 1) / (1 - p);
-
- double ans = 1e18;
- _for(i, 1, n)
- {
- int k = n / i, r = n % i;
- ans = min(ans, r * dp[k + 1] + (i - r) * dp[k] + i * x);
- }
- printf("Case #%d: %.6f\n", ++kase, ans);
- }
-
- return 0;
- }
首先要写出方程,这个是简单的。注意这道题要求的是走过的边数的期望,而实际上三种情况只有一种情况会增加一条边,所以写方程时不要直接写1
写出方程后发现有dp[1]和dp[fa[i]]两个未知量,那么就设dp[i] = aidp[1]+bidp[fa[i]],然后代入dp方程中,化成dp[1]和dp[fa[i]]的形式,得到递推式。然后求一下叶子节点的式子即可。
无解的话就是除0的情况。因为一般情况下是可以算出来的,只有分母为0是不能的。
- #include<bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e4 + 10;
- double k[N], e[N], a[N], b[N], c[N], t[N];
- vector<int> g[N];
- int n;
-
- bool dfs(int u, int fa)
- {
- if(g[u].size() == 1 && g[u][0] == fa)
- {
- a[u] = k[u];
- b[u] = 1 - e[u] - k[u];
- c[u] = 1 - e[u] - k[u];
- return true;
- }
-
- double suma = 0, sumb = 0, sumc = 0;
- t[u] = (1 - e[u] - k[u]) / g[u].size();
- for(int v: g[u])
- {
- if(v == fa) continue;
- if(!dfs(v, u)) return false;
- suma += a[v];
- sumb += b[v];
- sumc += c[v];
- }
- if(fabs(1 - t[u] * sumb) < 1e-8) return false;
- a[u] = (k[u] + t[u] * suma) / (1 - t[u] * sumb);
- b[u] = t[u] / (1 - t[u] * sumb);
- c[u] = (t[u] * sumc + 1 - e[u] - k[u]) / (1 - t[u] * sumb);
- return true;
- }
-
- int main()
- {
- scanf("%d", &n);
- _for(i, 1, n - 1)
- {
- int u, v;
- scanf("%d%d", &u, &v);
- g[u].push_back(v);
- g[v].push_back(u);
- }
- _for(i, 1, n)
- {
- int x, y;
- scanf("%d%d", &x, &y);
- k[i] = 1.0 * x / 100;
- e[i] = 1.0 * y / 100;
- }
-
- if(!dfs(1, 0) || fabs(1 - a[1]) < 1e-8) puts("impossible");
- else printf("%.10f\n", c[1] / (1 - a[1]));
-
- return 0;
- }
这题真的把各种背包结合起来
强制选一个的操作很秀
- #include<bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 100 + 10;
- int dp[N][N], w[N], v[N], n, T, m, s;
-
- int main()
- {
- while(~scanf("%d%d", &n, &T))
- {
- memset(dp, 0, sizeof dp);
- _for(i, 1, n)
- {
- scanf("%d%d", &m, &s);
- _for(j, 1, m) scanf("%d%d", &w[j], &v[j]);
-
- if(s == 0)
- {
- _for(j, 0, T) dp[i][j] = -1e9; //因为必须取一个有不合法的状态,所以初始化为负无穷
- _for(k, 1, m)
- for(int j = T; j >= w[k]; j--)
- dp[i][j] = max(dp[i][j], max(dp[i][j - w[k]] + v[k], dp[i - 1][j - w[k]] + v[k])); //强制选一个,以及和前面的状态做背包
- }
- else if(s == 1)
- {
- _for(j, 0, T) dp[i][j] = dp[i - 1][j];
- _for(k, 1, m)
- for(int j = T; j >= w[k]; j--)
- dp[i][j] = max(dp[i][j], dp[i - 1][j - w[k]] + v[k]);
- }
- else
- {
- _for(j, 0, T) dp[i][j] = dp[i - 1][j];
- _for(k, 1, m)
- for(int j = T; j >= w[k]; j--)
- dp[i][j] = max(dp[i][j], dp[i][j - w[k]] + v[k]);
- }
- }
- printf("%d\n", (dp[n][T] < 0) ? -1: dp[n][T]);
- }
-
- return 0;
- }
现在多刷刷cf的题,关键是学解题技巧。
首先是位运算的题很常见的思路就是每一位独立出来考虑,这道题也是一样
首先若边为0,那么肯定端点都是0,这是可以确定的,赋值。
然后考虑什么时候字典序最小,那么就从前往后,每一位看能不能为0
必须为1只有一种情况,就是边为1,而另一个端点为0。对于当前点,前面的点在已经确定了,为了尽量使得当前可以为1,我们可以把后面的位先全部设为1(除了零边赋值为0)
那么就遍历当前点所有的边,看是否有不得不为1的情况,即一边且另一个端点是0。这样一位一位即可。有一个细节就是可能自己向自己连边,这个时候是一边的话那这一位就肯定为1了。
- #include<bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 2e5 + 10;
- int ans[N], t[N], n, m;
- vector<pair<int, int>> g[N];
- int main()
- {
- scanf("%d%d", &n, &m);
- while(m--)
- {
- int i, j, x;
- scanf("%d%d%d", &i, &j, &x);
- if(i > j) swap(i, j);
- g[i].push_back({j, x});
- g[j].push_back({i, x});
- }
-
- _for(j, 0, 30)
- {
- _for(i, 1, n) t[i] = 1;
- _for(i, 1, n)
- for(auto x: g[i])
- if(!((x.second >> j) & 1))
- t[i] = t[x.first] = 0;
- _for(i, 1, n)
- {
- if(!t[i]) continue;
- int flag = 1;
- for(auto x: g[i])
- if(!t[x.first] || i == x.first)
- {
- flag = 0;
- break;
- }
- if(flag) t[i] = 0;
- }
- _for(i, 1, n)
- if(t[i])
- ans[i] |= 1 << j;
- }
- _for(i, 1, n) printf("%d ", ans[i]);
-
- return 0;
- }
这是一类题,暴力看似不可行,实际上是可行的,加一些小优化,或者是复杂度计算。
这题的关键就是发现计算的过程中会出现很多0,把0单独考虑可以大大加快速度,这样暴力也能过。
考虑0的话,维护最后的0的位置,每次计算和排序都考虑后面的数,然后继续维护0的位置。注意可能有当前没有0的情况,要判断一下。
- #include<bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e5 + 10;
- int a[N], b[N], n;
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d", &n);
- _for(i, 1, n) scanf("%d", &a[i]);
-
- int pos = 0;
- _for(i, 1, n)
- if(a[i])
- {
- pos = i - 1;
- break;
- }
-
- while(n > 1 && a[n - 1])
- {
- _for(i, max(pos, 1), n) a[i] = a[i + 1] - a[i];
- n--;
- sort(a + max(pos, 1), a + n + 1);
- if(pos) pos--;
- while(pos + 1 <= n && !a[pos + 1]) pos++;
- }
- printf("%d\n", a[n]);
- }
-
- return 0;
- }
思路挺妙,首先左边问号全部左括号,右边全部右括号一定是一个解
然后看有无第二个解,那就把中间那里交换一下,也就是最右边的左括号和最左边的右括号,这样是影响最小的,然后看这样是否合法。
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- bool check(string s)
- {
- int len = s.size(), cnt = 0;
- rep(i, 0, len)
- {
- if(s[i] == '(') cnt++;
- else cnt--;
- if(cnt < 0) return false;
- }
- return true;
- }
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- string s;
- cin >> s;
- int len = s.size();
-
- vector<int> pos;
- int l = len / 2, r = len / 2;
- rep(i, 0, len)
- {
- if(s[i] == '(') l--;
- else if(s[i] == ')') r--;
- else pos.push_back(i);
- }
- rep(i, 0, l) s[pos[i]] = '(';
- rep(i, l, pos.size()) s[pos[i]] = ')';
-
- int ans = 0;
- if(l && r)
- {
- swap(s[pos[l - 1]], s[pos[l]]);
- if(check(s)) ans = 1;
- }
- puts(ans ? "NO" : "YES");
- }
-
- return 0;
- }
这题不难,但写的时候写错了一个细节,调了很久才发现
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- typedef long long ll;
- const int N = 1e5 + 10;
- ll h[N], a[N];
- int n;
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d", &n);
- _for(i, 1, n) scanf("%lld", &h[i]);
- _for(i, 2, n - 1) a[i] = max(max(h[i - 1], h[i + 1]) + 1 - h[i], 0ll);
-
- if(n % 2 == 1)
- {
- ll ans = 0;
- for(int i = 2; i <= n; i += 2) ans += a[i];
- printf("%lld\n", ans);
- continue;
- }
-
- vector<pair<ll, ll>> ve;
- for(int i = 2; i + 1 <= n; i += 2) ve.push_back({a[i], a[i + 1]});
- //这里两个两个打进去时,注意退出条件是i+1<=n 不是i <= n 要用这一块的终点而不是起点
-
- ll cur = 0;
- for(auto x: ve) cur += x.second;
- ll ans = cur;
- for(auto x: ve)
- {
- cur += x.first - x.second;
- ans = min(ans, cur);
- }
- printf("%lld\n", ans);
- }
-
- return 0;
- }
这题的关键在于,O(n^2)的算法看起来会T,实际上不会T
先判断a,b中有没有相等的,都有的话直接输出答案,否则去重,之后就暴力枚举a,b
因为要输出下标,我开始的做法是将值和下标绑在一起用pair,但是这种方法写起来复杂,而且交上去MLE了,应该是vector里面直接存下标即可。
学了学菜狗的代码,学到了很多
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e7 + 10;
-
- int main()
- {
- ios::sync_with_stdio(false);
- cin.tie(0);
-
- int n, m;
- cin >> n >> m;
- vector<int> a(n + 1), b(m + 1);
- _for(i, 1, n) cin >> a[i];
- _for(i, 1, m) cin >> b[i];
- vector<int> posa(N, -1), posb(N, -1), va, vb;
- array<int, 4> ans = {-1, -1, -1, -1};
- _for(i, 1, n)
- {
- if(posa[a[i]] == -1)
- {
- posa[a[i]] = i;
- va.push_back(i);
- }
- else
- {
- ans[0] = posa[a[i]];
- ans[1] = i;
- }
- }
- _for(i, 1, m)
- {
- if(posb[b[i]] == -1)
- {
- posb[b[i]] = i;
- vb.push_back(i);
- }
- else
- {
- ans[2] = posb[b[i]];
- ans[3] = i;
- }
- }
- if(ans[0] != -1 && ans[2] != -1)
- {
- cout << ans[0] << " " << ans[1] << " " << ans[2] << " " << ans[3] << "\n";
- return 0;
- }
-
- vector<array<int, 2>> vis(2 * N, {-1, -1});
- for(auto i : va)
- for(auto j: vb)
- {
- if(vis[a[i] + b[j]][0] == -1) vis[a[i] + b[j]] = {i, j};
- else
- {
- cout << i << " " << vis[a[i] + b[j]][0] << " " << j << " " << vis[a[i] + b[j]][1];
- return 0;
- }
- }
- cout << "-1\n" ;
-
- return 0;
- }
这道题是比较复杂的贪心,思维量比较大
首先不能直接从左到右先填r,再填e,再填e,比如r?d??? 先填r的话就会导致在前缀中d比e多
因此我们要先处理这种特殊情况,再贪心
首先对所有前缀需要满足cntr >= cnte >= cntd
对于所有后缀需要满足cntd >= cnte >= cntr
结合那个特例,可以化简成对前缀需要d >= e 对后缀需要e >= r 满足这两个条件即满足上面的式子
因为是对称的,所以我们只用考虑前缀,后缀类似处理即可。
对于前缀,统计e的数量和d数量,如果不满足d比e多,那就把最近一个问号填为e,显然e越靠中间越优,所以是最近的。后缀同理填e
这样填完后再进行r e d的贪心,最后再判断是否合法。
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- bool solve()
- {
- string s; cin >> s;
- int len = s.size();
- if(len % 3) return false;
-
- vector<int> pos;
- int cnte = 0, cntd = 0, cntr = 0;
- rep(i, 0, len)
- {
- if(s[i] == 'e') cnte++;
- else if(s[i] == 'd') cntd++;
- else if(s[i] == '?') pos.push_back(i);
- if(cnte < cntd)
- {
- if(!pos.size()) return false;
- s[pos.back()] = 'e'; pos.pop_back();
- cnte++;
- }
- }
- cntr = cnte = cntd = 0;
- pos.clear();
- for(int i = len - 1; i >= 0; i--)
- {
- if(s[i] == 'e') cnte++;
- else if(s[i] == 'r') cntr++;
- else if(s[i] == '?') pos.push_back(i);
- else cntd++;
- if(cnte < cntr)
- {
- if(!pos.size()) return false;
- s[pos.back()] = 'e'; pos.pop_back();
- cnte++;
- }
- }
-
- int r = len / 3 - cntr, e = len / 3 - cnte, d = len / 3 - cntd;
- if(r < 0 || e < 0 || d < 0) return false;
- rep(i, 0, len)
- if(s[i] == '?')
- {
- if(r) s[i] = 'r', r--;
- else if(e) s[i] = 'e', e--;
- else if(d) s[i] = 'd', d--;
- }
-
- cntr = cntd = cnte = 0;
- rep(i, 0, len)
- {
- if(s[i] == 'r') cntr++;
- if(s[i] == 'e') cnte++;
- if(s[i] == 'd') cntd++;
- if(!(cntr >= cnte && cnte >= cntd)) return false;
- }
- cntr = cntd = cnte = 0;
- for(int i = len - 1; i >= 0; i--)
- {
- if(s[i] == 'r') cntr++;
- if(s[i] == 'e') cnte++;
- if(s[i] == 'd') cntd++;
- if(!(cntd >= cnte && cnte >= cntr)) return false;
- }
- return true;
- }
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--) puts(solve() ? "Yes" : "No");
- return 0;
- }
比较需要思考的贪心,挺有趣的
先看两边最高的
首先如果左边最高的大于右边最高的,那就比一场。显然要以最小代价赢,这样赢得话两边得差是最小的,比较划算
如果左边最高的小于右边最高的,那么这样左边怎么样都赢不了,那就拿左边最差的去送,代价最小
如果左边最高的等于右边最高的,那就需要讨论一下
这时有两种方法,一种是最高的比一个平局,一个是拿左边最差的去比。
那么就需要讨论左边最差的那个有没有价值。
如果左边最差的小于右边最差的:如果最高的比一个平局,那么就是一个平局,最差的输了,减200。如果拿左边最差的去比,输200,然而左边最好的可能大于右边第二好的。所以第二种要更优秀。
如果左边最差的等于右边最差的,那么这时其实两种策略都是平手,都可以。
如果左边最差的强于右边最差的。如果第一种策略,那就是平局加上赢一场,加200。如果第二种策略,那就是输一场,然后左边最高的可能赢右边第二高的。第一种策略必为+200,第二种可能+200,选第一种。
所以综合一下上述的选择,可以发现就是最好的比一下,如果能赢就这么比,否则看最差的比一下,如果能赢就比,否则就最差的去比最好的。
其实就是分类讨论,然后分析那种策略最优,然后总结
- #include <cstdio>
- #include <algorithm>
- #include <functional>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e3 + 10;
- int a[N], b[N], n;
-
- int main()
- {
- while(scanf("%d", &n) && n)
- {
- _for(i, 1, n) scanf("%d", &a[i]);
- _for(i, 1, n) scanf("%d", &b[i]);
- sort(a + 1, a + n + 1, greater<int>());
- sort(b + 1, b + n + 1, greater<int>());
-
- int al = 1, ar = n, bl = 1, br = n, ans = 0;
- while(al <= ar)
- {
- if(a[al] > b[bl])
- {
- ans += 200;
- al++; bl++;
- }
- else if(a[ar] > b[br])
- {
- ans += 200;
- ar--; br--;
- }
- else
- {
- if(a[ar] < b[bl]) ans -= 200;
- ar--; bl++;
- }
- }
- printf("%d\n", ans);
- }
-
- return 0;
- }
因为数据范围比较小,所以可以考虑莫队。
我离散化那里卡了一下,因为不仅要把a[i]离散化,还有差为k这个限制
比较好写的做法是将a[i]-k,a[i]+k同样扔到离散化数组里面,这样就很好处理了
- #include <cstdio>
- #include <algorithm>
- #include <functional>
- #include <cmath>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- typedef long long ll;
- const int N = 27000 + 10;
- int a[N], L[N], R[N], lsh[N * 3], cnt, n, m, k, block, t;
- struct query
- {
- int l, r, bl, id;
- }q[N];
- ll ans[N], f[N * 3], sum;
-
- bool cmp(query x, query y)
- {
- if(x.bl != y.bl) return x.bl < y.bl;
- if(x.bl & 1) return x.r < y.r;
- return x.r > y.r;
- }
-
- int lowbit(int x) { return x & -x; }
-
- void modify(int x, int p)
- {
- for(; x <= t; x += lowbit(x))
- f[x] += p;
- }
-
- ll s(int x)
- {
- ll res = 0;
- for(; x; x -= lowbit(x))
- res += f[x];
- return res;
- }
-
- void add(int x)
- {
- sum += s(R[x]) - s(L[x] - 1);
- modify(a[x], 1);
- }
-
- void erase(int x)
- {
- modify(a[x], -1);
- sum -= s(R[x]) - s(L[x] - 1);
- }
-
- int main()
- {
- scanf("%d%d%d", &n, &m, &k);
- _for(i, 1, n)
- {
- scanf("%d", &a[i]);
- lsh[++cnt] = a[i];
- lsh[++cnt] = a[i] - k;
- lsh[++cnt] = a[i] + k;
- }
- sort(lsh + 1, lsh + cnt + 1);
- t = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
- _for(i, 1, n)
- {
- L[i] = lower_bound(lsh + 1, lsh + t + 1, a[i] - k) - lsh;
- R[i] = lower_bound(lsh + 1, lsh + t + 1, a[i] + k) - lsh;
- a[i] = lower_bound(lsh + 1, lsh + t + 1, a[i]) - lsh;
- }
-
- block = sqrt(n);
- _for(i, 1, m)
- {
- int l, r; scanf("%d%d", &l, &r);
- q[i] = {l, r, l / block, i};
- }
- sort(q + 1, q + m + 1, cmp);
-
- int l = 1, r = 0;
- _for(i, 1, m)
- {
- int ll = q[i].l, rr = q[i].r;
- while(l < ll) erase(l++);
- while(l > ll) add(--l);
- while(r < rr) add(++r);
- while(r > rr) erase(r--);
- ans[q[i].id] = sum;
- }
- _for(i, 1, m) printf("%lld\n", ans[i]);
-
- return 0;
- }
最优策略就是能配对的配对,否则仍掉
以手上的单牌和剩下牌的总数作为状态,用期望dp
注意边界情况
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- typedef long long ll;
- const int mod = 1e9 + 7;
- ll dp[20][150];
-
- ll binpow(ll a, ll b)
- {
- ll res = 1;
- for(; b; b >>= 1)
- {
- if(b & 1) res = res * a % mod;
- a = a * a % mod;
- }
- return res;
- }
-
- ll inv(ll x) { return binpow(x, mod - 2); }
-
- int main()
- {
- _for(i, 1, 13)
- _for(j, 3 * i, 123)
- dp[i][j] = (1 + 3 * i * inv(j) % mod * dp[max(i - 2, 0)][j - 1] % mod
- + (j - 3 * i) * inv(j) % mod * dp[i][j - 1] % mod) % mod;
-
- int T, kase = 0;
- scanf("%d", &T);
- while(T--)
- {
- string s; cin >> s;
- int len = s.size();
- map<string, int> mp;
- for(int i = 0; i < len; i += 2)
- mp[s.substr(i, 2)]++;
-
- int cnt = 0;
- for(auto x: mp)
- if(x.second == 1)
- cnt++;
- printf("Case #%d: %lld\n", ++kase, dp[cnt][123]);
- }
-
- return 0;
- }
比赛时当n为偶数,m为奇数的时候,构造了一下,发现构造不出来,就猜了一个肯定不成立,然后直接交了,A了。
赛后可以证明一下,首先除了最大的数,其他的数都是成对的
首先如果全部为一个数,那么n为偶数,和肯定为偶数,而m为奇数,排除
那么不是全部为一个数,除了最大数外,其他的数因为是成对的,所以和为偶数,那么剩下最大数的位置也是偶数的,那么最后的和为偶数,不可能m为奇数
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e5 + 10;
- int a[N], n, m;
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d%d", &n, &m);
- if(n % 2 == 1)
- {
- if(n > m) puts("No");
- else
- {
- _for(i, 1, n - 1) a[i] = 1, m--;
- a[n] = m;
- puts("Yes");
- _for(i, 1, n) printf("%d ", a[i]); puts("");
- }
- }
- else
- {
- if(m % 2 == 1) puts("No");
- else
- {
- if(n > m) puts("No");
- else
- {
- _for(i, 1, n - 2) a[i] = 1, m--;
- a[n - 1] = a[n] = m / 2;
- puts("Yes");
- _for(i, 1, n) printf("%d ", a[i]); puts("");
- }
- }
- }
- }
-
- return 0;
- }
有两种边,一种是括号匹配的,一种是匹配与匹配之间的。比赛时用并查集写的,实际上并不会成环,可以直接统计右括号和)(的个数。
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 2e5 + 10;
- int f[N], st[N], top, n, cnt;
-
- int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
-
- void merge(int x, int y)
- {
- int fx = find(x), fy = find(y);
- if(fx != fy)
- {
- cnt--;
- f[fx] = fy;
- }
- }
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d", &n);
- _for(i, 1, 2 * n) f[i] = i;
-
- top = 0;
- cnt = 2 * n;
- string s; cin >> s;
- s = " " + s;
- int pre;
- _for(i, 1, 2 * n)
- {
- if(s[i] == '(')
- {
- if(s[i - 1] == ')') merge(i, pre);
- st[++top] = i;
- }
- else
- {
- pre = i;
- merge(i, st[top--]);
- }
- }
- printf("%d\n", cnt);
- }
-
- return 0;
- }
这题等相通了发现不难,但比赛时还是想了一个小时,很不应该。
显然可以生成树一种颜色,非树边一种颜色。当非数边成环时,将一条边加到生成树中,生成树新成的环删去一条边即可
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 2e5 + 10;
- vector<pair<int, int>> g[N];
- int ans[N], vis[N], d[N], f[N], n, m;
- vector<array<int, 3>> edge;
- void dfs(int u, int fa)
- {
- d[u] = d[fa] + 1;
- vis[u] = 1;
- for(auto x: g[u])
- {
- int v = x.first, id = x.second;
- if(v == fa)
- {
- f[u] = id;
- continue;
- }
- if(vis[v])
- {
- int flag = 1;
- for(auto x: edge)
- if(x[2] == id)
- {
- flag = 0;
- break;
- }
- if(flag) edge.push_back({u, v, id});
- }
- else dfs(v, u);
- }
- }
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d%d", &n, &m);
- edge.clear();
- _for(i, 1, n) g[i].clear(), vis[i] = 0;
- _for(i, 1, m) ans[i] = 0;
-
- _for(i, 1, m)
- {
- int u, v;
- scanf("%d%d", &u, &v);
- g[u].push_back({v, i});
- g[v].push_back({u, i});
- }
-
- dfs(1, 0);
-
- int flag = 0;
- if(edge.size() == 3)
- {
- set<int> s;
- for(auto x: edge)
- {
- s.insert(x[0]);
- s.insert(x[1]);
- }
- if(s.size() == 3) flag = 1;
- }
- if(!flag) for(auto x: edge) ans[x[2]] = 1;
- else
- {
-
- ans[edge[0][2]] = ans[edge[1][2]] = 1;
- int u = edge[2][0], v = edge[2][1];
- if(d[u] < d[v]) swap(u, v);
- ans[f[u]] = 1;
- }
- _for(i, 1, m) printf("%d", ans[i]); puts("");
- }
-
- return 0;
- }
这题是由三个关键点嵌套起来。
1.花费为区间除以2向上取整
任何一个操作的花费都可以拆分成长度为1和长度为2的操作,这样考虑一定包含的最优的答案。这样拆分可以简化问题
这样子答案的上界就是n,每个数都区间长度为1的操作
为了让花费最小,显然要尽可能多的操作区间为2的且一起为0,也就是相等。
考虑对于ai-1 和ai 如果本来就不等,那么如果使它们相等呢。在ai-1之前可以一直操作区间为2的,比如说a6,它可以通过前面的操作变成a6^a5,a6^a5^a4,a6^a5^a4^a3……
那么怎么判断是否可以使得相等呢,这就用到了异或的性质,是第二个关键点
2.异或的性质
直接判断比较难,我们加入前缀异或和,也就是从1到ai-1,记为sum
那么也就是存在前缀异或和为sum ^ a[i],注意这个前缀异或和包括0
也就是对于一个ai,可以判断可能存在一个区间,使得答案更小。
那么就由很多个区间,就转化为区间覆盖问题
3.贪心求区间覆盖
结论是按照右端点排序,然后遍历,能放就放
因为可以按照右端点排序,所以可以直接枚举a1到an,然后如果当前可以使答案更小,那就操作,操作之后前缀异或和要重新记录,注意0始终存在。
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e5 + 10;
- int a[N], n;
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d", &n);
- _for(i, 1, n) scanf("%d", &a[i]);
-
- set<int> s;
- s.insert(0);
- int ans = n, sum = 0;
- _for(i, 1, n)
- {
- if(s.count(sum ^ a[i]))
- {
- ans--;
- s.clear(); s.insert(0);
- sum = 0;
- }
- else sum ^= a[i], s.insert(sum);
- }
- printf("%d\n", ans);
- }
-
- return 0;
- }
这题的关键在于反过来看,我一直按照题目的意思来看,结果感觉比较复杂。
逆向思考,其实就是从根节点,每次选一条红边往下到叶子,叶子就是胜者。
那么显然,要改变的话,只有改变这条路径上的边才有用,而且不同改变的结果一定是不同的,可以看作线段树向下,每次向左边或者向右边,只要有一次不同,结果一定不同。
考虑改变能产生的不同的结果,修改0次就是c(n, 0) 修改1次就是c(n,1)……
令k=min(k, n) 那么结果一共有c(n, 0) + c(n,1)……c(n, k)这么多可能
那改变的人肯定取这么多可能的最大值,那么布置的人一定是布置1,2,3……这样可以使得最大值最小。
所以最终的答案就是这个和
预处理阶乘即可
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- typedef long long ll;
- const int N = 1e5 + 10;
- const int mod = 1e9 + 7;
- ll f[N];
-
- ll binpow(ll a, ll b)
- {
- ll res = 1;
- for(; b; b >>= 1)
- {
- if(b & 1) res = res * a % mod;
- a = a * a % mod;
- }
- return res;
- }
-
- ll inv(ll x) { return binpow(x, mod - 2); }
-
- ll C(ll n, ll m)
- {
- return f[n] * inv(f[m]) % mod * inv(f[n - m]) % mod;
- }
-
- int main()
- {
- f[0] = 1;
- _for(i, 1, 1e5) f[i] = f[i - 1] * i % mod;
-
- int n, k;
- scanf("%d%d", &n, &k);
- k = min(k, n);
-
- ll ans = 0;
- _for(i, 0, k) ans = (ans + C(n, i)) % mod;
- printf("%lld\n", ans);
-
- return 0;
- }
可以发现只有两种情况,要不是链式,要不是4号为根,然后1 2 3连向4
第一种情况很好处理,第二种情况需要解方程。如果解出小数或者小于等于0就无解。
最后还要注意一下点数和边数要满足限制。
写的时候有想到,但是以为不会超,但实际上是会超的,WA了一发。其实写了也没错,应该一开始就写上去。
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- int n, d12, d23, d13, cnt, root;
- vector<pair<int, int>> ans;
- int dis[5][5];
-
- void add(int u, int x)
- {
- vector<int> ve;
- ve.push_back(u);
- _for(i, 1, x - 1) ve.push_back(cnt++);
- ve.push_back(root);
- rep(i, 0, ve.size() - 1) ans.push_back({ve[i], ve[i + 1]});
- }
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- cnt = 4;
- ans.clear();
- scanf("%d%d%d%d", &n, &d12, &d23, &d13);
- dis[1][2] = dis[2][1] = d12;
- dis[2][3] = dis[3][2] = d23;
- dis[1][3] = dis[3][1] = d13;
-
- //case 1
- vector<pair<int, int>> ve;
- ve.push_back({d12, 3});
- ve.push_back({d23, 1});
- ve.push_back({d13, 2});
- sort(ve.begin(), ve.end());
- if(ve[0].first + ve[1].first == ve[2].first)
- {
- root = ve[2].second;
- _for(i, 1, 3)
- if(i != root)
- add(i, dis[i][root]);
- }
- else //case 2
- {
- if((d13 + d12 - d23) % 2 != 0)
- {
- puts("NO");
- continue;
- }
- int a, b, c;
- a = (d13 + d12 - d23) / 2;
- c = d13 - a;
- b = d23 - c;
- if(a <= 0 || b <= 0 || c <= 0)
- {
- puts("NO");
- continue;
- }
-
- root = cnt++;
- if(root > n)
- {
- puts("NO");
- continue;
- }
- add(1, a); add(2, b); add(3, c);
- }
- while(cnt <= n) ans.push_back({cnt++, root});
- if(cnt > n + 1) puts("NO");
- else
- {
- puts("YES");
- for(auto [u, v]: ans) printf("%d %d\n", u, v);
- }
- }
-
- return 0;
- }
妙啊,我自己思考的时候就觉得操作1和操作2会使得序列的某个性质改变,通过这个性质判断
一开始想的是和,但是都是使得和不变的
突破口是操作2的下标不同,所以我们把下标加入到序列的性质中,也就是ai * i的求和,可以发现操作2使得这个值+1,操作一不变,通过这个即可得出答案
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- typedef long long ll;
- const int N = 1e5 + 10;
- vector<pair<ll, int>> ve;
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- ve.clear();
- int n, m;
- scanf("%d%d", &n, &m);
- _for(i, 1, n)
- {
- ll sum = 0;
- _for(j, 1, m)
- {
- ll x; scanf("%lld", &x);
- sum += x * j;
- }
- ve.push_back({sum, i});
- }
- sort(ve.begin(), ve.end());
- printf("%d %lld\n", ve.back().second, ve.back().first - ve[0].first);
- }
-
- return 0;
- }
我的思路是找所有点的lca,然后从这开始搜,看经过这一点的路径最大值是否为k。
题目的思路简单一些,找深度最大的节点dfs,这个节点一定是路径的一端,然后后dfs能否走过所有选择的点
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 2e5 + 10;
- vector<int> g[N];
- int d[N], up[N][21], c[N], n, q, k, ans, root;
-
- void dfs(int u, int fa)
- {
- d[u] = d[fa] + 1;
- up[u][0] = fa;
- _for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
-
- for(int v: g[u])
- {
- if(v == fa) continue;
- dfs(v, u);
- }
- }
-
- int lca(int u, int v)
- {
- if(d[u] < d[v]) swap(u, v);
- for(int j = 20; j >= 0; j--)
- if(d[up[u][j]] >= d[v])
- u = up[u][j];
- if(u == v) return u;
- for(int j = 20; j >= 0; j--)
- if(up[u][j] != up[v][j])
- u = up[u][j], v = up[v][j];
- return up[u][0];
- }
-
- int dfs2(int u, int fa)
- {
- int mx1 = 0, mx2 = 0;
- for(int v: g[u])
- {
- if(v == fa) continue;
- int t = dfs2(v, u);
- if(t > mx1) mx2 = mx1, mx1 = t;
- else mx2 = max(mx2, t);
- }
- if(u == root && c[u] + mx1 + mx2 == k) ans = 1;
- return c[u] + mx1;
- }
-
- int main()
- {
- scanf("%d", &n);
- _for(i, 1, n) g[i].clear();
- _for(i, 1, n - 1)
- {
- int u, v;
- scanf("%d%d", &u, &v);
- g[u].push_back(v);
- g[v].push_back(u);
- }
- dfs(1, 0);
-
- scanf("%d", &q);
- _for(i, 1, q)
- {
- root = 0;
- _for(i, 1, n) c[i] = 0;
- scanf("%d", &k);
- _for(j, 1, k)
- {
- int x; scanf("%d", &x);
- c[x] = 1;
- if(!root) root = x;
- else root = lca(root, x);
- }
- ans = 0;
- dfs2(root, 0);
- puts(ans ? "YES" : "NO");
- }
-
- return 0;
- }
首先可以算出一个可能的取值范围,注意当分数小于一个值,转化为开区间可以向下取整+1
然后就变成了怎么给每个区间分配点,我在这里卡住了,想把区间排序,然后不太行。
正解是反过来思考,给点分配区间,从1遍历到n,对于当前点,给它分配一个右端点最小的区间。
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 5e5 + 10;
- vector<int> v[N];
- int b[N], a[N], n;
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d", &n);
- _for(i, 1, n) v[i].clear();
- _for(i, 1, n)
- {
- scanf("%d", &b[i]);
- int l = i / (b[i] + 1) + 1;
- v[l].push_back(i);
- }
-
- set<pair<int, int>> s;
- _for(i, 1, n)
- {
- for(int id: v[i]) s.insert({(b[id] == 0) ? n : id / b[id], id});
- a[s.begin()->second] = i;
- s.erase(s.begin());
- }
- _for(i, 1, n) printf("%d ", a[i]); puts("");
- }
-
- return 0;
- }
交互题做的非常少……
这题提供的主要信息就是一段区间右多少种字符
考虑一个一个字符来猜,如果前面字符已知,现在猜第i个字符
考虑f(j, i) 和f(j, i-1) f(x,y)表示[x, y]中有多少种字符
f(j, i) 和f(j, i-1)的差别就是多了一个s[i]
显然,如果s[i]在(j,i-1)未出现过,那么f(j, i)会大1,否则相等
当j离i越近,(j,i-1)就越小,越难包含s[i],如果j离i越远,那么就越可能包含s[i]
考虑f(j, i) - f(j, i - 1) 那么随着j的增大,它的值一定是0 0 0 0 0 1 1 1 1
最右边的0的位置就是s[i]
因为0011具有单调性,所以我们可以二分来找这个s[i]
当然,右可能前面每出现过找不到,此时就询问i
那么这种做法询问是比较多的,因为有log(1000) 6000的数据范围意味着每个i最多6次
考虑优化,显然只有每个字母最后出现的位置是有意义的,那么我们可以只存这个,那么二分次数就大大减小。
- #include <bits/stdc++.h>
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- const int N = 1e3 + 10;
- int pos[30], cnt, n;
- char ans[N];
-
- char ask1(int x)
- {
- printf("? 1 %d\n", x);
- fflush(stdout);
- string s; cin >> s; //输入一个字符时要额外小心 应该这么写
- return s[0];
- }
-
- int ask2(int l, int r)
- {
- printf("? 2 %d %d\n", l, r);
- fflush(stdout);
- int t; scanf("%d", &t);
- return t;
- }
-
- int main()
- {
- scanf("%d", &n);
- ans[1] = ask1(1);
- pos[++cnt] = 1;
- _for(i, 1, n)
- {
- sort(pos + 1, pos + cnt + 1);
- int l = 0, r = cnt + 1;
- while(l + 1 < r)
- {
- int m = l + r >> 1;
- if(ask2(pos[m], i) == (cnt - m + 1)) l = m;
- else r = m;
- }
- if(l == 0)
- {
- ans[i] = ask1(i);
- pos[++cnt] = i;
- }
- else
- {
- ans[i] = ans[pos[l]];
- pos[l] = i;
- }
- }
-
- printf("! ");
- _for(i, 1, n) putchar(ans[i]);
- fflush(stdout);
-
- return 0;
- }
这题妙啊,有两种做法。
第一种做法比较直接,但写起来复杂一些,第二种不太好想到,但是写起来简单
第一种做法
首先,有一个贪心猜测,每一次走尽可能的远。我一开始有这个想法,但不知道这个贪心是不是对的。其实很好证明,如果u最远能达到v,那么u和v中间的节点一定不能连一条边超过v。因为如果可以,比如中间有个节点a,连一条边到v右边的b,那么可以发现u是可以连边到b的,就不满足v是最远的这个前提。
有这个结论后,考虑怎么迅速求这个最远的点,比如当前节点是i,那么a[i]与a[i+1]的大小关系显然决定了a[i]是最大值还是最小值,如果a[i] > a[i+1]那么显然a[i]要作为最大值
如果a[i]是作为最大值的话,设j为离i最近的使得a[j] > a[i],那么可能得答案在[i + 1, j - 1]中
这个j可以用单调栈O(n)求出
在可能的答案区间中,求最小值,这个最小值就是我们要的答案。因为不可能有边从i越过这个最小值,因为i已经是最小值了。所以可以用线段树预处理区间最值,同时存对应的下标(用pair写起来非常简洁),那么就可以迅速求出
每次跳一次的复杂度是线段树的O(logn),而每次至少可以往右移动一个,那么最多跳n次,所以复杂度是O(nlogn)的
- #include <bits/stdc++.h>
- #define l(k) (k << 1)
- #define r(k) (k << 1 | 1)
- #define rep(i, a, b) for(int i = (a); i < (b); i++)
- #define _for(i, a, b) for(int i = (a); i <= (b); i++)
- using namespace std;
-
- typedef pair<int, int> pa;
- const int N = 2.5e5 + 10;
- int a[N], rmax[N], rmin[N], st[N], top, n;
- pa tmax[N << 2], tmin[N << 2];
-
- void up(int k)
- {
- tmax[k] = max(tmax[l(k)], tmax[r(k)]);
- tmin[k] = min(tmin[l(k)], tmin[r(k)]);
- }
-
- void build(int k, int l, int r)
- {
- if(l == r)
- {
- tmax[k] = tmin[k] = {a[l], l};
- return;
- }
- int m = l + r >> 1;
- build(l(k), l, m);
- build(r(k), m + 1, r);
- up(k);
- }
-
- pa ask_max(int k, int l, int r, int L, int R)
- {
- if(L <= l && r <= R) return tmax[k];
- int m = l + r >> 1;
- pa res = {0, 0};
- if(L <= m) res = max(res, ask_max(l(k), l, m, L, R));
- if(R > m) res = max(res, ask_max(r(k), m + 1, r, L, R));
- return res;
- }
-
- pa ask_min(int k, int l, int r, int L, int R)
- {
- if(L <= l && r <= R) return tmin[k];
- int m = l + r >> 1;
- pa res = {1e9, 0};
- if(L <= m) res = min(res, ask_min(l(k), l, m, L, R));
- if(R > m) res = min(res, ask_min(r(k), m + 1, r, L, R));
- return res;
- }
-
- int main()
- {
- int T; scanf("%d", &T);
- while(T--)
- {
- scanf("%d", &n);
- _for(i, 1, n) scanf("%d", &a[i]);
-
- top = 0;
- for(int i = n; i >= 1; i--) //单调栈不用先加入 直接从n开始
- {
- while(top && a[i] > a[st[top]]) top--;
- rmax[i] = top ? st[top] : n + 1; //注意栈未空的情况
- st[++top] = i;
- }
- top = 0;
- for(int i = n; i >= 1; i--)
- {
- while(top && a[i] < a[st[top]]) top--;
- rmin[i] = top ? st[top] : n + 1;
- st[++top] = i;
- }
-
- build(1, 1, n);
- int ans = 0, cur = 1;
- while(cur != n)
- {
- if(a[cur + 1] > a[cur])
- {
- pa t = ask_max(1, 1, n, cur + 1, rmin[cur] - 1);
- cur = t.second;
- }
- else
- {
- pa t = ask_min(1, 1, n, cur + 1, rmax[cur] - 1);
- cur = t.second;
- }
- ans++;
- }
- printf("%d\n", ans);
- }
-
- return 0;
- }
第二种做法
核心是一个点:不会有边跨过最值
对于ai=n,一定不会有边跨过它,对于aj=1也是
不能跨国它意味着一定要经过它
同时i和j存在一条边
假设i 所以dis(1, n) = dis(1, i) + 1 + dis(j, n) 那么可以这样一直递归下去,只不过最值变成了当前区间的最值 这里的区间要不是l=1,要不是r=n,所以只需要处理前缀最值和后缀最值 还要存值对应的下标 这题的关键在于把题目给的式子变换,拆开 分类一下把那个绝对值拆开,发现最值一定是4个点之一,所以有用的只有4个点D. Lena and Matrix(拆式子)