PDF 题解以及测试数据和标程已发至牛客各群~
A - ★★比赛新机制★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★☆☆ ☆☆☆☆☆ |
标签:<label>递推</label><label>前缀和</label>
题解
记 ,先考虑正序的情况:
若答题顺序为 ,那么总罚时为
;
若答题顺序为 ,那么总罚时为
。
不难发现,,以此类推,可得
,反序同理。
于是,在每次递推时更新最小值即可。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define maxn 500005 #define INF 123456789000000000 ll T, n, sum, isum, risum, a[maxn], ans; int main() { scanf("%lld", &T); while (T--) { scanf("%lld", &n); sum = isum = risum = 0; ans = INF; for (ll i = 1; i <= n; i++) { scanf("%lld", &a[i]); sum += a[i]; isum += i * a[i]; risum += (n - i + 1) * a[i]; } for (ll i = n; i >= 1; i--) { isum += sum - n * a[i]; ans = min(ans, isum); } for (ll i = 1; i <= n; i++) { risum += sum - n * a[i]; ans = min(ans, risum); } printf("%lld\n", ans); } return 0; }
B - ★★体育课排队★★
时间限制: | 4000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★★ ★★☆☆☆ |
标签:<label>二分图</label><label>网络流</label><label>二分答案</label><label style="background-color:#f39c11">Special Judge</label>
题解
根据题意,考虑将 位同学视为二分图的左部,将
个位置视为二分图的右部,将该问题转化为二分图匹配问题。
由于匹配是同时开始进行的,因此每次二分图匹配的时间只与最长的那组匹配有关,要求该时间的最小值,使用 等方法难以解决。于是考虑二分答案,每次只将所有范围之内的边加入二分图,然后进行二分图匹配。若能够完全匹配,说明在当前时间约束下可以完成排队任务;否则应扩大时间约束。
使用匈牙利 算法,总时间复杂度
,无法通过。
注意到该图为二分图,可以用优化过的网络流算法进行二分图匹配,单次复杂度只有 ,相比匈牙利
算法的
具有明显优势。匹配完成后,利用残余网络按要求输出匹配情况。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; #define maxn 2005 #define maxm 2004005 #define INF 1234567890 int T, n, m, ss[maxn], sx[maxn], sy[maxn], sum[maxn], l, r, s, t, maxflow, cnt = 1, x[maxn], y[maxn], head[maxn], dis[maxn], cur[maxn], gap[maxn], ans[maxn]; struct Edge {int u, v, w, pre, next;} edge[maxm]; inline void add(int u, int v, int w) { edge[++cnt].u = u; edge[cnt].v = v; edge[cnt].w = w; edge[cnt].pre = w; edge[cnt].next = head[u]; head[u] = cnt; return; } inline int Dfs(int u, int lim) { if (!lim || u == t) return lim; int flow = 0, f; for (int i = cur[u]; i; i = edge[i].next) { int v = edge[i].v, w = edge[i].w; cur[u] = i; if (dis[v] + 1 == dis[u] && (f = Dfs(v, min(lim, w)))) { flow += f; lim -= f; edge[i].w -= f; edge[i ^ 1].w += f; if (!lim) return flow; } } gap[dis[u]]--; if (!gap[dis[u]]) dis[s] = t + 1; dis[u]++; gap[dis[u]]++; return flow; } inline void Bfs() { queue<int>q; for(int i = 1; i <= t; i++) { dis[i] = INF; gap[i] = 0; } dis[t] = 0; gap[0] = 1; q.push(t); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; i; i = edge[i].next) { int v = edge[i].v; if (dis[v] >= INF) { dis[v] = dis[u] + 1; gap[dis[v]]++; q.push(v); } } } return; } inline void ISAP() { Bfs(); while (dis[s] < t) { for (int i = 1; i <= t; i++) cur[i] = head[i]; maxflow += Dfs(s, INF); } } inline bool Check(int mid) { cnt = 1; maxflow = 0; for (int i = 1; i <= t; i++) head[i] = dis[i] = cur[i] = gap[i] = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) for (int k = 1; k <= ss[j]; k++) { int w = abs(sx[j] + k - 1 - x[i]) + abs(sy[j] - y[i]); if (w > mid) continue; add(i, n + sum[j-1] + k, 1); add(n + sum[j-1] + k, i, 0); } for(int i = 1; i <= n; i++) { add(s, i, 1); add(i, s, 0); add(i + n, t, 1); add(t, i + n, 0); } ISAP(); if (maxflow == n) return 1; else return 0; } int main() { cin >> T; while (T--) { cin >> n >> m; l = 0, r = 5000; for (int i = 1; i <= n; i++) cin >> x[i] >> y[i]; for (int i = 1; i <= m; i++) { cin >> ss[i] >> sx[i] >> sy[i]; sum[i] = sum[i-1] + ss[i]; } s = 2 * n + 1; t = 2 * n + 2; while (l < r) { int mid = (l + r) >> 1; if (Check(mid)) r = mid; else l = mid + 1; } cout<< l << endl; Check(l); for (int i = 1; i <= n; i++) for (int j = head[i]; j; j = edge[j].next) { int v = edge[j].v, w = edge[j].w, pre = edge[j].pre; if (pre > w) ans[v - n] = i; } for(int i = 1; i <= m; i++) for(int j = 1; j <= ss[i]; j++) cout << ans[sum[i-1] + j] << " \n"[j == ss[i]]; } return 0; }
C - ★★生命的游戏★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★☆☆☆ ☆☆☆☆☆ |
标签:<label>暴力</label><label>模拟</label>
题解
按要求暴力模拟即可。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; #define maxn 105 int T, n, k, ans, a[maxn][maxn], now[maxn][maxn], s[maxn][maxn], di[] = {-1, -1, -1, 0, 0, 1, 1, 1}, dj[] = {-1, 0, 1, -1, 1, -1, 0, 1}; inline bool Check() { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (a[i][j] != now[i][j]) return 0; return 1; } inline void Solve() { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) s[i][j] = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (now[i][j]) { for (int k = 0; k < 8; k++) { int ni = (i + di[k] + n) % n, nj = (j + dj[k] + n) % n; s[ni][nj]++; } } for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (s[i][j] == 3) now[i][j] = 1; else if (s[i][j] < 2 || s[i][j] > 3) now[i][j] = 0; } int main() { cin >> T; while (T--) { cin >> n >> k; ans = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { cin >> a[i][j]; now[i][j] = a[i][j]; } do { Solve(); ans++; } while (!Check() && --k); if (k) cout << "YES" << endl << ans << endl; else cout << "NO" << endl; } return 0; }
D - ★★飞马祝福语★★
时间限制: | 4000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★★ ★★★☆☆ |
标签:<label>递推</label><label>动态规划</label><label>区间动态规划</label><label>矩阵</label><label>线段树</label>
题解
设祝福信为 ,
FeiMa
为 ,下标均从
开始。
令 表示
的前
位以
FeiMa
中第 个字符结尾的不同子序列的数量(
表示没有出现其中任何一个字符),那么状态转移方程为:
$dp[n][5]$。
暴力求解,时间复杂度 ,无法通过。
考虑用矩阵进行状态转移,设 ,
若
F
,那么有:
$s[i]=
s[i]
6$ 种转移矩阵。
于是将问题转化为矩阵的区间乘积维护,考虑将矩阵作为线段树结点的元素。
进行区间修改使用矩阵快速幂求值然后赋值,总时间复杂度 ,无法通过。
考虑预处理出 种转移矩阵的
次方,在每次区间修改时可以直接
赋值。
时间复杂度 。
另外,此题还有复杂度更优的算法:线段树维护区间动态规划。
设祝福信为 ,
FeiMa
为 ,
的下标从
开始,
的下标从
开始。
使用线段树维护区间动态规划。令 表示线段树的
节点包含的字符子串范围内,拥有字符串
的
位到
位区间的子序列数量 (例如,
表示
节点中
F
的数量, 表示节点中
e
的数量, 表示节点中子序列为
iM
的数量),那么状态转移方程为:
$dp[1][0][4]
O(5^{3}q \log{n})$。
参考代码
C++ - 1
#include<bits/stdc++.h> using namespace std; #define maxn 100005 #define p 998244353 struct Matrix { int a[6][6], n = 5; Matrix(const int & N = 5) : n(N) {memset(a, 0, sizeof(a));} void operator = (const Matrix & x) { n = x.n; for(int i = 0; i <= n; i++) for(int j = 0; j <= n; j++) a[i][j] = x.a[i][j]; } Matrix operator * (const Matrix & x) const { Matrix ans = Matrix(n); for(int i = 0;i <= n; i++) for(int j = 0; j <= n; j++) for(int k = 0; k <= n; k++) ans.a[i][j] = (ans.a[i][j] + 1ll * a[i][k] * x.a[k][j]) % p; return ans; } }; int T, n, q, a[maxn]; char x; map<char, int> m; Matrix pre[6][maxn]; struct Tree {Matrix mul; int tag;}t[maxn << 2]; inline int ls(int k) {return k << 1;} inline int rs(int k) {return k << 1 | 1;} inline void push_up(int l, int r, int k) { t[k].mul = t[ls(k)].mul * t[rs(k)].mul; } inline void push_down(int l, int r, int k) { if (t[k].tag != -1) { t[ls(k)].tag = t[k].tag; t[rs(k)].tag = t[k].tag; int mid = (l + r) >> 1; t[ls(k)].mul = pre[t[k].tag][mid - l + 1]; t[rs(k)].mul = pre[t[k].tag][r - mid]; t[k].tag = -1; } } inline void build(int l, int r, int k) { t[k].tag = -1; if (l == r) { t[k].mul = pre[a[l]][1]; return; } int mid = (l + r) >> 1; build(l, mid, ls(k)); build(mid + 1, r, rs(k)); push_up(l, r, k); } inline void update(int nl, int nr, int l, int r, int k, int x) { if (nl <= l && r <= nr) { t[k].mul = pre[x][r - l + 1]; t[k].tag = x; return; } push_down(l, r, k); int mid = (l + r) >> 1; if (nl <= mid) update(nl, nr, l, mid, ls(k), x); if (nr > mid) update(nl, nr, mid + 1, r, rs(k), x); push_up(l, r, k); } int main() { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); for (int k = 0; k <= 5; k++) { for(int i = 0; i <= 5; i++) pre[k][0].a[i][i] = pre[k][1].a[i][i] = 1; if (k) pre[k][1].a[k-1][k] = 1; } for(int k = 0;k <= 5; k++) for(int i = 2;i < maxn; i++) pre[k][i] = pre[k][i - 1] * pre[k][1]; m['F'] = 1, m['e'] = 2,m['i'] = 3,m['M'] = 4,m['a'] = 5; cin >> T; while (T--) { cin >> n >> q; for (int i = 1; i <= n; i++) { cin >> x; a[i] = m[x]; } build(1, n, 1); int l, r; while (q--) { cin >> l >> r >> x; update(l, r, 1, n, 1, m[x]); cout << t[1].mul.a[0][5] << endl; } } return 0; }
C++ - 2
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int mod = 998244353; const int maxn = 1e5+100; int T,n,m,q; char ss[maxn]; char pp[] = "FeiMa"; ll pro[maxn<<2][5][5]; char lazy[maxn<<2]; void build(int rt,int l,int r) { lazy[rt] = 0; if(l==r) { for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt][i][j] = 0; for(int i=0; i<5; i++) pro[rt][i][i] = ss[l]==pp[i]; return; } int mid = (l+r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); for(int i=0; i<5; i++) { for(int j=i; j<5; j++) { pro[rt][i][j] = (pro[rt<<1][i][j]+pro[rt<<1|1][i][j])%mod; for(int k=i; k<j; k++) pro[rt][i][j] = (pro[rt][i][j]+pro[rt<<1][i][k]*pro[rt<<1|1][k+1][j])%mod; } } } void push_down(int rt,int l,int r) { if(!lazy[rt]) return; lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt]; int mid = (l+r)>>1; int sz = mid-l+1; for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt<<1][i][j] = 0; for(int i=0; i<5; i++) pro[rt<<1][i][i] = (lazy[rt]==pp[i])*sz; sz = r-mid; for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt<<1|1][i][j] = 0; for(int i=0; i<5; i++) pro[rt<<1|1][i][i] = (lazy[rt]==pp[i])*sz; lazy[rt] = 0; } void update(int rt,int l,int r,int fr,int to,char ch) { if(fr<=l && r<=to) { for(int i=0; i<5; i++) for(int j=i; j<5; j++) pro[rt][i][j] = 0; lazy[rt] = ch; int sz = r-l+1; for(int i=0; i<5; i++) pro[rt][i][i] = (ch==pp[i])*sz; return; } push_down(rt,l,r); int mid = (l+r)>>1; if(mid>=fr) update(rt<<1,l,mid,fr,to,ch); if(mid+1<=to) update(rt<<1|1,mid+1,r,fr,to,ch); for(int i=0; i<5; i++) { for(int j=i; j<5; j++) { pro[rt][i][j] = (pro[rt<<1][i][j]+pro[rt<<1|1][i][j])%mod; for(int k=i; k<j; k++) pro[rt][i][j] = (pro[rt][i][j]+pro[rt<<1][i][k]*pro[rt<<1|1][k+1][j])%mod; } } } char ch[2]; int main() { scanf("%d",&T); while(T--) { scanf("%d%d%s",&n,&q,ss + 1); build(1,1,n); int l,r; while(q--) { scanf("%d%d%s",&l,&r,ch); update(1,1,n,l,r,ch[0]); printf("%lld\n",pro[1][0][4]); } } return 0; }
E - ★★序列大团结★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★★ ☆☆☆☆☆ |
标签:<label>哈希Hash</label>
题解
先考虑序列中无法被 整除的数,设
为序列前
项中数值
出现的次数模
的余数,当
和
完全相同时(即每个数值
出现的次数模
的余数都相同),子序列
就是“团结”的。
考虑使用滚动数组,将 优化至
,从首项开始遍历并维护
,同时对
的内容进行
处理,将处理后的结果保存,记
为到目前为止
出现的次数,那么以当前项为右端点的团结的子序列的数量为
。
可用
存储。
而能够被 整除的数,不影响
值,但也同样需要计算答案。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; #define p 998244353 ull T, n, k, x, ans, now, hs; map<ull, int>num; map<int, int>s; inline ull ksm(ull x, ull k) { ull res = 1; for(; k; k >>= 1, x = x * x) if (k & 1) res = res * x; return res; } int main(){ cin >> T; while (T--) { cin >> n >> k; num.clear(); s.clear(); ans = now = 0; num[0] = 1; for (int i = 1; i <= n; i++) { cin >> x; if (x % k) { hs = ksm(p, x); now -= hs * s[x]; s[x] = (s[x] + 1) % k; now += hs * s[x]; } ans += num[now]; num[now]++; } cout << ans << endl; } return 0; }
F - ★★飞马分隔符★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★☆☆☆ ☆☆☆☆☆ |
标签:<label>模拟</label><label>字符串</label><label>贪心</label>
题解
从头到尾遍历,每次遇到字符 a
,判断前面是否顺次出现过一组 F
、e
、i
、M
,若出现过,则在此处分隔,更新答案,并清空记录。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; int T, n, ans, flag; string s; int main() { cin >> T; while (T--) { cin >> n >> s; ans = flag = 0; for (int i = 0; i < n; i++) { if (s[i] == 'F' && flag == 0) flag++; else if (s[i] == 'e' && flag == 1) flag++; else if (s[i] == 'i' && flag == 2) flag++; else if (s[i] == 'M' && flag == 3) flag++; else if (s[i] == 'a' && flag == 4) { ans++; flag = 0; } } cout << ans << endl; } return 0; }
G - ★★糖果魔法阵★★
时间限制: | 2000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★★ ★★★★☆ |
标签:<label>数学</label><label>数论</label><label>递推</label><label>二次剩余</label><label>光速幂</label><label>扩展欧拉定理</label><label>逆元</label>
题解
根据题意,定义 ,求出两个实数不动点为
。
构造
推出通项为
由于 是模
的二次剩余,将
与
代入得:
考虑指数 ,根据欧拉定理,可对其取模,即
考虑指数 ,根据扩展欧拉定理,当
时可对其取模,即
。
使用快速幂求出每天结束时的 ,进而求出下一天的魔力值,时间复杂度
,无法通过。
考虑对 进行模
的光速幂处理,对
和
进行模
的光速幂处理。
在每天结束时,用光速幂求出相应的 ,便可得知下一天的情况。
直到最后一天结束时,用光速幂和费马小定理求出 。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define maxn 31625 #define two 116195171 #define ka 116195172 #define kb 882049183 #define phi 402653184 #define p 998244353 ll s, d, q, n, a[maxn + 5][2], b[maxn + 5][2], c[maxn + 5][2], k, t, sum, ans; bool flag; inline void Init() { a[0][0] = a[0][1] = b[0][0] = b[0][1] = c[0][0] = c[0][1] = 1; for (ll i = 1; i <= maxn; i++) { a[i][0] = a[i - 1][0] * ka % p; b[i][0] = b[i - 1][0] * kb % p; c[i][0] = c[i - 1][0] * 4 % (p - 1); } for (ll i = 1; i <= maxn; i++) { a[i][1] = a[i - 1][1] * a[maxn][0] % p; b[i][1] = b[i - 1][1] * b[maxn][0] % p; c[i][1] = c[i - 1][1] * c[maxn][0] % (p - 1); } } inline ll ksm(ll x, ll k) { ll res = 1 % p; x %= p; for (; k; k >>= 1, x = x * x % p) if (k & 1) res = res * x % p; return res; } int main() { Init(); cin >> s >> d >> q; while (q--) { cin >> n; sum = s; k = flag = 0; for (ll i = 1; i <= n; i++) { k += sum / d; if (k >= phi) flag = 1; k %= phi; if(flag) k += phi; t = c[k % maxn][0] * c[k / maxn][1] % (p - 1); sum = (((a[t % maxn][0] * a[t / maxn][1] % p) + (b[t % maxn][0] * b[t / maxn][1] % p)) * two % p) * i % p; } ans = (sum * ksm(n, p - 2) % p) * ksm((((a[t % maxn][0] * a[t / maxn][1] % p) - (b[t % maxn][0] * b[t / maxn][1] % p) + p) % p) % p, p - 2)%p; cout << ans << endl; } return 0; }
H - ★★温暖的力量★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★☆☆☆☆ ☆☆☆☆☆ |
标签:<label>数学</label>
题解
要求将 分为尽可能多的质数的和。
当 为大于
的奇数时,可以分为若干个
和一个
;当
为大于
的偶数时,可以分为若干个
。
不难得出答案:
$$
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; int T, n; int main() { cin >> T; while (T--) { cin >> n; if (n <= 3) cout << -1 << endl; else cout << n / 2 << endl; } return 0; }
I - ★★平形四边行★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★★ ☆☆☆☆☆ |
标签:<label>暴力</label><label style="background-color:#f39c11">Special Judge</label>
题解
四个点能形成“平形四边行”的充要条件是,存在一种方案,将四个点均分为两组,每组的两个点形成一条线段,这两条线段的中点重合。
注意到桶的大小只有 ,即第
个点落下时,一定存在重合的中点,从而构成一组解。另外,桶不可能被完全填满,因此复杂度远远小于预期。
于是 暴力枚举每组点,存储所形成线段的中点,直到存在重合的中点,同时应注意过滤掉存在交集的两组点。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; #define maxn 100005 int n, x[maxn], y[maxn]; pair<int, int>v[4005][4005]; int main() { cin >> n; for (int i = 1; i <= n; i++) { cin >> x[i] >> y[i]; for (int j = 1; j < i; j++) { int xx = x[i] + x[j] + 2000; int yy = y[i] + y[j] + 2000; if (i == v[xx][yy].first || j == v[xx][yy].first) continue; if (i == v[xx][yy].second || j == v[xx][yy].second) continue; if (v[xx][yy].first) { cout << "YES" << endl << i << ' ' << j << ' ' << v[xx][yy].first << ' ' << v[xx][yy].second << endl; return 0; } v[xx][yy] = make_pair(i,j); } } cout << "NO" << endl; return 0; }
J - ★★翻滚吧硬币★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★☆ ☆☆☆☆☆ |
标签:<label>数学</label><label>计算几何</label><label style="background-color:#f39c11">Special Judge</label>
题解
硬币悖论问题。
如图,三枚硬币 ,半径分别为
。
现在,让硬币 沿着硬币
的边界翻滚,硬币
的圆心的运动轨迹为曲线
,记其长度为
,那么翻滚的圈数即为
。
由弧长公式可得 ,其中
可由余弦定理推出:$
$。
要想使得圈数最小,只需固定半径较小的两个硬币,让半径最大的硬币沿其边界翻滚即可。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; const long double pi=acosl(-1); int T, r[5]; long double ans, a, b, c, x, y; int main() { cin >> T; while (T--) { cin >> r[1] >> r[2] >> r[3]; sort(r + 1, r + 3 + 1); a = r[1] + r[2], b = r[1] + r[3], c = r[2] + r[3]; x = acosl((a * a + b * b - c * c) / (2.0l * a * b)); y = acosl((a * a + c * c - b * b) / (2.0l * a * c)); ans = ((pi - x) * b + (pi - y) * c) / (pi * r[3]); cout << fixed << setprecision(15) << ans << endl; } return 0; }
K - ★★快乐苹果树★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★★ ★★★★☆ |
标签:<label>数学</label><label>逆元</label><label>概率论</label><label>数学期望</label><label>树链剖分</label><label>树状数组</label><label>线段树</label>
题解
定义 表示以结点
为根的子树的大小,
表示结点
的 所有直接子结点(距离为
)组成的集合,
表示以结点
为根的子树内所有节点组成的集合。
对于操作一,可分为以下两种情况:
当选取结点
,那么点集
表示的是
,此时该操作与
无关,只与
有关。结点
的概率为
,于是点集
中所有结点的快乐值增加
。
当选取结点
时,概率为
,点集
表示的是
,于是点集
中所有结点的快乐值增加
。
预处理出各子树大小以及对应逆元,暴力求解,总时间复杂度 ,无法通过。
考虑使点集 中所有结点的快乐值都加上该值,然后再使点集
中所有结点的快乐值减去该值。
如此一来,对于每次操作一,使点集 中所有结点的快乐值增加
,
随后,遍历 ,使点集
中所有结点的快乐值减去
。
总时间复杂度 ,无法通过。
考虑将子树按大小分块,在每次进行子树更新时使用线段树或树状数组维护,总时间复杂度 ,可以通过,但不稳定且代码实现较为困难。
考虑进行树链剖分,将轻儿子 懒标记的值放置在
结点即
结点,重儿子
更新。查询时,遍历
节点,减去对应的懒标记的值。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define maxn 100005 #define maxm 200005 #define p 998244353 ll T, n, q, inv[maxn], t[maxn], tag[maxn], fa[maxn], se[maxn], son[maxn], dfn[maxn], top[maxn], id, cnt, head[maxn], s1[maxn], s2[maxn], s3[maxn]; struct Edge {ll u, v, next;} edge[maxm]; inline void add(ll u, ll v) { edge[++cnt].u = u; edge[cnt].v = v; edge[cnt].next = head[u]; head[u] = cnt; } inline void Get_Inv() { inv[1] = 1; for (ll i = 2; i < maxn; i++) inv[i] = (p - p / i) * inv[p % i] % p; } inline ll lowbit(ll x) {return x & (-x);} inline void change(ll x, ll val) { for (ll i = x; i <= n; i += lowbit(i)) t[i] = (t[i] + val) % p; } inline ll query(ll x) { ll res = 0; for (ll i = x; i; i -= lowbit(i)) res = (res + t[i]) % p; return res; } inline void update(ll l, ll r, ll val) { change(l, val); change(r + 1, p - val); } inline void Dfs1(int u, int f) { fa[u] = f; se[u] = 1; son[u] = s3[u] = 0; for (ll i = head[u]; i; i = edge[i].next) { ll v = edge[i].v; if (v == f) continue; Dfs1(v, u); se[u] += se[v]; if (se[v] > se[son[u]]) son[u] = v; } s1[u] = inv[se[u]]; for (ll i = head[u]; i; i = edge[i].next) { ll v = edge[i].v; if (v == f) continue; s2[v] = (se[v] * s1[u] % p) * inv[se[u] - se[v]] % p; s3[u] = (s3[u] + s2[v]) % p; } } inline void Dfs2(int u, int t) { top[u] = t; dfn[u] = ++id; if (!son[u]) return; Dfs2(son[u], t); for (ll i = head[u]; i; i = edge[i].next) { ll v = edge[i].v; if (v == son[u] || v == fa[u]) continue; Dfs2(v, v); } } int main() { Get_Inv(); cin >> T; while (T--) { cin >> n >> q; cnt = id = 0; for (ll i = 0; i <= n; i++) head[i] = t[i] = tag[i] = 0; ll u, v; for (ll i = 1; i < n; i++) { cin >> u >> v; add(u, v); add(v, u); } Dfs1(1, 0); Dfs2(1, 1); ll op, f, k; while (q--) { cin >> op; if (op == 1) { cin >> f >> k; update(dfn[f], dfn[f] + se[f] - 1, (s3[f] * k % p + (s1[f] * s1[f] % p) * k % p) % p); if (son[f]) update(dfn[son[f]], dfn[son[f]] + se[son[f]] - 1, (p - s2[son[f]] * k % p) % p); tag[f] = (tag[f] + k) % p; } else { cin >> f; ll res = query(dfn[f]); for(ll i = top[f]; fa[i]; i = top[fa[i]]) res = (res + (p - s2[i] * tag[fa[i]] % p) % p) % p; cout << res << endl; } } } return 0; }
L - ★★星星收集者★★
时间限制: | 1000ms |
---|---|
空间限制: | 256MB |
难度: | ★★★★★ ★☆☆☆☆ |
标签:<label>博弈论</label><label>动态规划</label>
题解
令星星的总和为 。
首先,因为双方的星星总和一定是 ,如果
,那么当
时
就获胜了。
反之,如果 ,当
时
获胜。
其余情况 获胜。
那么我们从 的角度考虑,他的策略根据
与
的关系,只有两种:
抓尽可能少的星星
抓尽可能多的星星(也就是全部抓取)
需要让
在这两种策略下都不能获胜。
令添加星星数为 ,首先可以发现,当
时,
必胜(全部抓取),所以可以得到
。
当 时,考虑
在添加星星时的策略。
我们发现,在此情况下,双方都想最小化自己的星星数。那么 会尽可能的将
分配给
,只要将
全部分配给第一个盒子就能做到。
我们用动态规划可以求出在原先状态下,a最少可以抓取多少星星。
令这个值为 ,则能得到
。
综合两种情况就能得到 的最小值。
时间复杂度 。
参考代码
C++
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define maxn 200005 ll n, x, a[maxn], sum[maxn], dp[maxn], last; int main() { cin >> n >> x; for (int i = 1; i <= n; i++) { cin >> a[i]; sum[i] = sum[i - 1] + a[i]; } last = n; dp[n] = a[n]; for (ll i = n - 1; i; i--) { dp[i] = sum[n] - sum[i - 1] - dp[last]; if (dp[i] > dp[last]) last = i; } cout << max(max(0ll, sum[n] - dp[1] - dp[1] + 1), max(0ll, 2 * x + 1 - sum[n])) << endl; return 0; }