ARC108
E 题意:给定长$n$排列,初始每个数未被标记,每次操作随机选一个未被标记的且标记后满足标记数单调的数标记,如果不存在这样的数结束操作,求期望操作数
考虑区间dp,$f_{l,r}$表示区间$[l,r]$左右端点标记时的期望操作数,那么假设$[l+1,r-1]$中可选点个数为$k$,如果$k=0$那么$f_{l,r}=0$,否则$f_{l,r}=1+sumlimits_{i=1}^k (f_{l,s_i}+f_{s_i,r})/2$,可以用树状数组优化到$O(n^2log{n})$
#include <bits/stdc++.h> using namespace std; const int N = 2e3+10, P = 1e9+7; int n, a[N], dp[N][N], inv[N]; int L[N][N],R[N][N],f[N][N]; int main() { scanf("%d", &n); inv[1] = 1; for (int i=2; i<=n; ++i) inv[i] = inv[P%i]*(int64_t)(-P/i)%P; for (int i=1; i<=n; ++i) scanf("%d", &a[i]); a[n+1] = n+1; for (int len=1; len<=n+2; ++len) { for (int l=0,r=len-1; r<=n+1; ++l,++r) { for (int t=a[r]; t<n+2; t |= t+1) ++f[l][t]; if (len<=2||a[l]+1>a[r]-1) continue; int cnt = 0, w = 0; for (int x=a[r]-1; x>=0; x = (x&(x+1))-1) { w = (w+L[l][x]+(int64_t)R[r][x])%P; cnt += f[l][x]; } for (int x=a[l]; x>=0; x = (x&(x+1))-1) { w = (w-L[l][x]-(int64_t)R[r][x])%P; cnt -= f[l][x]; } if (!cnt) dp[l][r] = 0; else dp[l][r] = (w*(int64_t)inv[cnt]+1)%P; for (int t=a[r]; t<n+2; t |= t+1) L[l][t] = (L[l][t]+dp[l][r])%P; for (int t=a[l]; t<n+2; t |= t+1) R[r][t] = (R[r][t]+dp[l][r])%P; } } if (dp[0][n+1]<0) dp[0][n+1] += P; printf("%d ", dp[0][n+1]); }
F 题意:给定树,把每个点涂上$0$或$1$,假设任意两$0$点最大距离为$X$,任意两$1$点最大距离为$Y$,那么贡献为$max(X,Y)$,求$2^N$种涂色方案的贡献和
先求出树的直径$(u,v)$,假设$u$和$v$同色,那么贡献为直径长度
异色的话,假设贡献为$D$
如果点$x$满足$dis(u,x)>Dwedge dis(v,x)>D$那么一定不合法
如果点$x$满足$dis(u,x)le D wedge dis(v,x)le D$那么这个点可以涂$0$或$1$
其余点都只能涂$1$种颜色
如果答案为$D$,那么对于第二类点还要满足至少$1$个染成距离为$D$的颜色,这个可以容斥求一下
#include <bits/stdc++.h> using namespace std; const int N = 2e5+10, P = 1e9+7; int n, fa[N], vis[N], pw[N], dep[2][N], f[N], h[N]; vector<int> g[N]; int bfs(int x, int d[]) { for (int i=1; i<=n; ++i) vis[i] = 0; queue<int> q; fa[x] = d[x] = 0, vis[x] = 1, q.push(x); while (q.size()) { x = q.front(); q.pop(); for (int y:g[x]) if (!vis[y]) { vis[y] = 1, fa[y] = x, d[y] = d[x]+1; q.push(y); } } return x; } int main() { scanf("%d", &n); for (int i=1; i<n; ++i) { int u, v; scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } int u = bfs(1,dep[0]), v = bfs(u,dep[1]); bfs(v,dep[0]); pw[0] = 1; int ma = 0; for (int i=1; i<=n; ++i) { pw[i] = pw[i-1]*2%P; if (i!=u&&i!=v) { ma = max(ma, min(dep[0][i], dep[1][i])); ++f[max(dep[0][i], dep[1][i])]; } } int ans = pw[n-2]*(int64_t)dep[0][u]%P, now = 0; for (int D=0; D<=dep[0][u]; ++D) { now += f[D]; if (D<ma) continue; h[D] = pw[now]; if (D) ans = (ans+(h[D]-h[D-1])*(int64_t)D)%P; } ans = ans*2%P; if (ans<0) ans += P; printf("%d ", ans); }
Dwango Programming Contest 6th
C 题意:$n$个小孩,$k$天,第$i$天随机选$a_i$个小孩发一块曲奇,假设最终第$i$个小孩拿了$c_i$块曲奇,求$c_1 imes c_2 imes ... imes c_n$的期望乘上$inom{n}{a_1} imesinom{n}{a_2} imes ... imes inom{n}{a_k}$
期望乘总方案,那么也就是要求所有方案下的贡献和
先写下刚看到这个题时的一些想法
假设所有$a_i$都为$1$的话,那么很容易做,对于一个确定的序列$c$,方案数就是$frac{k!}{prodlimits_{i=1}^n c_i!}$
所以答案也就是$k![x^k]prodlimits_{i=1}^nsumlimits_{jge 0} frac{j}{j!} x^{j}=k![x^k](e^{nx}x^n)=frac{k!n^{k-n}}{(k-n)!}$
那么如果$a_i$不全是$1$,那么方案数感觉很难处理,没想出来什么好办法
然后说下正解做法
考虑一个转化:$k imes n$的格子,先对于每个$k$,把第$k$行选$a_k$个格子放上棋子,然后再把每列选恰好一个棋子涂黑,求方案数
这样的话就可以简单dp了,设$f_{i,j}$表示前$i$行涂黑了$j$个棋子的方案数,枚举下一行涂黑多少列转移即可
$dp$转移是一个卷积形式,也可以$O(knlog{n})$来做
然后还有一个问题是,如果初始每个小孩曲奇数不为$0$的话,是否可做呢?
#include <bits/stdc++.h> using namespace std; const int N = 1e3+10, K = 22, P = 1e9+7; int n, k, a[N], f[K][N], C[N][N]; void add(int &a, int64_t b) {a=(a+b)%P;} int main() { scanf("%d%d", &n, &k); for (int i=1; i<=k; ++i) scanf("%d", &a[i]); for (int i=0; i<=n; ++i) { C[i][0] = 1; for (int j=1; j<=i; ++j) C[i][j] = (C[i-1][j]+C[i-1][j-1])%P; } f[0][0] = 1; for (int i=1; i<=k; ++i) { for (int j=0; j<=n; ++j) { int &ret = f[i-1][j]; if (!ret) continue; for (int x=0; x<=a[i]&&x+j<=n; ++x) { add(f[i][j+x], C[n-j][x]*(int64_t)C[n-x][a[i]-x]%P*ret); } } } printf("%d ", f[k][n]); }
gym102006K
图的最小着色问题,把每条边定向,然后求mex,由于每个点出度不超过$2$,所以答案$le 3$
所以如果是二分图的话答案就是$2$,否则只能是$3$
gym102439L
题意:范围$[1,2n]$的数,每人分$2$个,随机分给$n$个人,求和的最大值唯一的概率
打个表可以发现和的最大值不变时,方案数是不变的
假设最大值是$i+j$,可以发现分布是$xxxxxixxjxx$的话很好计算,直接枚举最后三个数匹配,剩余数即可任意匹配
ARC107F
题意:$n$点$m$边无向图,每个点$i$有权值$(A_i,B_i)$,可以任选一些点删除,花费是删点的$A$值和,删点后对应边也删除,最终每个连通块得分为$B$值和的绝对值,求最大化得分和-花费
每个点有三种情况,取$b_i$或取$-b_i$或删除,并且要求每个连通块正负要相同。
答案先加上$sum |b_i|$,然后构造最小割即可。
同一个连通块符号相同,可以拆点,对于边$(u,v)$连$(u,v+n,INF),(v,u+n,INF)$
对于$b_ile 0$的连边$(S,i,0),(i,i+n,a+b),(i+n,T,2b)$,否则连$(S,i,-2b),(i,i+n,-b+a),(i+n,T,0)$
#include <bits/stdc++.h> using namespace std; const int N = 1e6+10, S = N-2, T = N-1, INF = 0x3f3f3f3f; int n, m, a[N], b[N]; struct edge { int to,w,next; edge(int to=0,int w=0,int next=0):to(to),w(w),next(next) {} } e[N]; int head[N], dep[N], cur[N], cnt=1; queue<int> Q; int bfs() { for (int i=1; i<=2*n; ++i) dep[i]=INF,cur[i]=head[i]; dep[S]=INF,cur[S]=head[S]; dep[T]=INF,cur[T]=head[T]; dep[S]=0,Q.push(S); while (Q.size()) { int u = Q.front(); Q.pop(); for (int i=head[u]; i; i=e[i].next) { if (dep[e[i].to]>dep[u]+1&&e[i].w) { dep[e[i].to]=dep[u]+1; Q.push(e[i].to); } } } return dep[T]!=INF; } int dfs(int x, int w) { if (x==T) return w; int used = 0; for (int i=cur[x]; i; i=e[i].next) { cur[x] = i; if (dep[e[i].to]==dep[x]+1&&e[i].w) { int f = dfs(e[i].to,min(w-used,e[i].w)); if (f) used+=f,e[i].w-=f,e[i^1].w+=f; if (used==w) break; } } return used; } int dinic() { int ans = 0; while (bfs()) ans+=dfs(S,INF); return ans; } void add(int u, int v, int w) { e[++cnt] = edge(v,w,head[u]); head[u] = cnt; e[++cnt] = edge(u,0,head[v]); head[v] = cnt; } int main() { scanf("%d%d", &n, &m); int tot = 0; for (int i=1; i<=n; ++i) scanf("%d", &a[i]); for (int i=1; i<=n; ++i) scanf("%d", &b[i]), tot += abs(b[i]); for (int i=1; i<=m; ++i) { int u, v; scanf("%d%d", &u, &v); add(u+n,v,INF); add(v+n,u,INF); } for (int i=1; i<=n; ++i) { if (b[i]>=0) add(i,i+n,b[i]+a[i]),add(i+n,T,2*b[i]); else add(S,i,-2*b[i]),add(i,i+n,-b[i]+a[i]); } printf("%d ", tot-dinic()); }
gym101982I
题意:给定序列,每个元素范围$[1,k]$,有些位置为$0$表示不确定的数,要求填上$[1,k]$使得逆序对最大
首先可以发现填数一定是非降的,那么就可以得到一个dp做法
设${dp}_{i,j}$表示前$i$个位置,最后一个位置填的数是$j$的最大值
先除去已有的数之间的逆序对贡献,那么每次枚举填数的数和填的长度来转移
那么有${dp}_{i,j}=maxlimits_{0le x<i,j<yle k+1} {dp}_{x,y}+calc(x+1,i,j)$
$calc(l,r,j)$表示$[l,r]$中填$j$时对已经填好的数的贡献
这个$dp$方程化简一下发现可以斜率优化到$O(nk)$
#include <bits/stdc++.h> using namespace std; const int N = 2e5+10; int n, k, cur, a[N], sum[N], cnt[N][105]; int64_t dp[N][2],f[N]; deque<int> q; int64_t dy(int a, int b) { return sum[a]*(int64_t)sum[a]+f[a]-dp[a][cur^1]-sum[b]*(int64_t)sum[b]-f[b]+dp[b][cur^1]; } int64_t dx(int a, int b) { return sum[a]-sum[b]; } int chk(int a, int b, int c) { return dy(a,b)*dx(b,c)<=dy(b,c)*dx(a,b); } int main() { scanf("%d%d", &n, &k); for (int i=1; i<=n; ++i) { scanf("%d", &a[i]); memcpy(cnt[i],cnt[i-1],sizeof cnt[0]); sum[i] = sum[i-1]+!a[i]; if (a[i]) { for (int j=a[i]; j<=k; ++j) ++cnt[i][j]; } } int64_t ans = 0; for (int i=1; i<=n; ++i) if (a[i]) ans += cnt[i-1][k]-cnt[i-1][a[i]]; if (!sum[n]) return printf("%lld ", ans),0; for (int i=1; i<=n; ++i) dp[i][0] = -1e12; for (int j=k; j; --j) { for (int i=1; i<=n; ++i) { f[i] = f[i-1]; if (!a[i]) f[i] += cnt[i-1][k]-cnt[i-1][j]+cnt[n][j-1]-cnt[i][j-1]; } q.clear(); q.push_back(0); cur ^= 1; for (int i=1; i<=n; ++i) dp[i][cur] = -1e12; for (int i=1; i<=n; ++i) { if (a[i]) { dp[i][cur] = dp[i-1][cur]; continue; } while (q.size()>1&&dy(q[1],q[0])<=dx(q[1],q[0])*sum[i]) q.pop_front(); dp[i][cur] = dp[q[0]][cur^1]+f[i]-f[q[0]]+sum[q[0]]*(sum[i]-(int64_t)sum[q[0]]); dp[i][cur] = max(dp[i][cur], dp[i][cur^1]); while (q.size()>1&&chk(i,q.back(),q[q.size()-2])) q.pop_back(); q.push_back(i); } } printf("%lld ", dp[n][cur]+ans); }
hitachi2020
C. ThREE
题意:给定树,求构造一个排列$p$,使得树上距离为$3$的点对$(i,j)$满足$p_i$与$p_j$的和或积是$3$的倍数
首先对树二分图染色,假设两部大小为$X,Y,X<Y$,那么如果$lfloorfrac{n}{3} floorge X$,那么把所有$3$的倍数分到$X$中一定合法
否则的话把模$3$为$1$的数的个数一定$<X$,可以全放进$X$中,不够的话再放进$3$的倍数
ABC155E
题意:设$f(x)$表示$x$十进制下的数位和,给定$N$,求$xge N$时$f(x)+f(x-N)$的最小值
可以发现最优解的最高位要么等于$N$要么比$N$大$1$,相等的话可以直接递归到后面求解
不等的话那么相当于把后面每位的数$a$变为$9-a$,个位数变为$10-a$
所以很容易用dp求解
#include <bits/stdc++.h> using namespace std; const int N = 1e6+10; int n,dp[N][2]; char a[N]; int main() { scanf("%s", a+1); n = strlen(a+1); for (int i=1; i<=n; ++i) a[i] -= '0'; reverse(a+1,a+1+n); dp[1][0] = a[1], dp[1][1] = 10-a[1]; for (int i=2; i<=n+1; ++i) { dp[i][0] = dp[i-1][0]+a[i]; dp[i][1] = dp[i-1][1]+9-a[i]; if (a[i]!=9) dp[i][0] = min(dp[i][0],dp[i-1][1]+a[i]+1); if (a[i]) dp[i][1] = min(dp[i][1],dp[i-1][0]+9-a[i]+1); } printf("%d ", min(dp[n+1][0],dp[n+1][1])); }
ABC182F
题意:$n$种硬币价格$A_i$,保证$A_{i+1}>A_{i}$并且$A_{i+1}$是$A_i$的倍数,$A_1=1$,有一个价格$X$的商品,花$Y(ge X)$元买,找回$Y-X$元,要求$Y$和$Y-X$都用最少的硬币表示时,花出的每种硬币都不会在找回,求有多少种合法的$Y$
相当于是说给定了一个变进制数$X$,求有多少个变进制数$Y(ge 0)$,使得$Y$和$X+Y$非零位交集为空,分是否进位dp即可
#include <bits/stdc++.h> using namespace std; const int N = 55; int n; int64_t X, a[N], lim[N], f[N], dp[N][2]; int main() { scanf("%d%lld", &n, &X); for (int i=1; i<=n; ++i) scanf("%lld", &a[i]); for (int i=1; i<n; ++i) lim[i] = a[i+1]/a[i]; for (int i=n; i; --i) { f[i] = X/a[i]; X %= a[i]; } dp[0][0] = 1; for (int i=1; i<n; ++i) { dp[i][0] = dp[i-1][0]; dp[i][1] = dp[i-1][1]; if (f[i]) dp[i][1] += dp[i-1][0]; if (f[i]+1!=lim[i]) dp[i][0] += dp[i-1][1]; } printf("%lld ", dp[n-1][0]+dp[n-1][1]); }
ABC155F
题意:$N$个炸弹,给定初始位置和状态,$M$根线,每根线连接一个区间的炸弹,切断的话会改变改变炸弹的状态,求怎样切能使炸弹状态全为$0$
考虑炸弹状态的差分,添加$B[0]=B[N+1]=0$,那么全为$0$就等价于$[1,N+1]$的差分全为$0$
考虑一根线$[L,R]$对差分序列的影响,会改变位置$L$和$R+1$的差分值
那么问题就转化为给定一张无向图,要求保留一些边,使得每个连通块的异或和为$0$
假设原图中存在一个连通块异或和非零,那么无论怎样删边一定无解
假设所有连通块异或和均为零,那么一定有解,因为考虑一棵dfs树,回溯时如果当前点所处连通块状态为$0$就断开与父亲的边,这样最终转移到根时,根的状态一定为$0$
#include <bits/stdc++.h> using namespace std; const int N = 1e5+10; int n, m, val[N], b[N], vis[N]; pair<int,int> a[N]; vector<int> ans; vector<pair<int,int>> g[N]; int dfs(int x) { vis[x] = 1; for (auto e:g[x]) { int y = e.first; if (!vis[y]&&dfs(y)) val[x] ^= 1, ans.push_back(e.second); } return val[x]; } int main() { scanf("%d%d", &n, &m); for (int i=1; i<=n; ++i) scanf("%d%d", &a[i].first, &a[i].second); sort(a+1,a+1+n); for (int i=1; i<=n+1; ++i) { val[i] = a[i].second^a[i-1].second; b[i] = a[i].first; } for (int i=1; i<=m; ++i) { int l, r; scanf("%d%d", &l, &r); l = lower_bound(b+1,b+1+n,l)-b; r = upper_bound(b+1,b+1+n,r)-b; g[l].push_back({r,i}); g[r].push_back({l,i}); } for (int i=1; i<=n; ++i) if (!vis[i]) { if (dfs(i)) return puts("-1"),0; } sort(ans.begin(),ans.end()); printf("%d ", (int)ans.size()); for (int i=0; i<ans.size(); ++i) printf("%d%c", ans[i], " "[i+1==ans.size()]); }
gym101667
H 简单biset本来十几分钟就敲完了,结果被卡常,又改成NTT还是T,最后发现长度2e5改成1e5就过了
#pragma GCC optimize("Ofast") #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native") #pragma GCC optimize("unroll-loops") #include <bits/stdc++.h> using namespace std; const int N = 1e5+10; int n, m; char s[N], t[N]; bitset<N> ch[3],a[3]; int main() { scanf("%d%d%s%s", &n, &m, s+1, t+1); for (int i=1; i<=n; ++i) { if (s[i]=='S') s[i] = 'R'; else if (s[i]=='P') s[i] = 'S'; else s[i] = 'P'; } for (int i=1; i<=n; ++i) { if (s[i]=='P') ch[0].set(i); else if (s[i]=='S') ch[1].set(i); else ch[2].set(i); } for (int i=1; i<=m; ++i) { if (t[i]=='P') a[0].set(i); else if (t[i]=='S') a[1].set(i); else a[2].set(i); } int ma = 0; for (int i=0; i<n; ++i) { int cnt = 0; for (int z=0; z<3; ++z) cnt += (a[z]&ch[z]>>i).count(); ma = max(ma, cnt); } printf("%d ", ma); }
L
题意:$p$个人,$p$个有向图,每个人初始在对应图点$1$,终点是对应图的点$s$,每个时刻一个人可以走一条边或者在一个点停留,走边和停留的花费给定,要求$p$个人同时出发,同时到达终点,求最少花费
最优解对应时刻一定不超过$n1 imes n2 imes n3$,$dp$求出每个人每个时刻到达终点的花费就完了,证明暂时还不会
K
题意:走$n$步,给定每次走的距离和方向,求调整每次走的距离使得路径不交叉
处理一下每步走的方向,如果$i+2$和$i$的方向相同,那么第$i$步走$1$,否则走$n-i+1$
#include <bits/stdc++.h> using namespace std; const int N = 1e4+10; int n, a[N], d[N]; int main() { scanf("%d", &n); for (int i=2; i<=n; ++i) scanf("%*d%d", &a[i]); scanf("%*d%*d"); if (n<=3) { for (int i=1; i<=n; ++i) printf("1%c", " "[i==n]); return 0; } for (int i=2; i<=n; ++i) d[i] = (d[i-1]+a[i]+4)%4; printf("1 "); for (int i=2; i<n; ++i) { if (d[i]==d[i+2]) printf("1 "); else printf("%d ", n-i+1); } puts("1"); }
G
题意:给定两条折线段$L,U$,求下界为$L$上界为$U$的面积
直接$n^2$可过,也可以写个二分或者双指针优化一下
#include <bits/stdc++.h> using namespace std; const int N = 3e5+10, M = 50010; int n, m; vector<int> x[2],y[2]; int main() { scanf("%d%d", &n, &m); for (int i=1; i<=2*n+1; ++i) { int t; scanf("%d", &t); if (i&1) y[0].push_back(t); else x[0].push_back(t); } for (int i=1; i<=2*m+1; ++i) { int t; scanf("%d", &t); if (i&1) y[1].push_back(t); else x[1].push_back(t); } if (y[0][0]<y[0][1]&&y[1][0]>y[1][1]||y[0][0]>y[0][1]&&y[1][0]<y[1][1]) return puts("0 0"),0; if (y[0][0]>y[0][1]&&y[1][0]>y[1][1]) { for (int &t:y[0]) t = M-t; for (int &t:y[1]) t = M-t; swap(x[0],x[1]),swap(y[0],y[1]); } auto get = [&](int tp, int l, int r, int h) { for (int i=0; i+1<y[tp].size(); ++i) { int u = y[tp][i], v = y[tp][i+1]; if (u<=h&&h<v) { if (l<=x[tp][i]&&x[tp][i]<r) return i; return -1; } } return -1; }; auto calc = [&](int tp, int l, int r, int h) { int64_t ans = 0; for (int i=l+1; i<=r; ++i) { int p = i==x[tp].size()?M:x[tp][i]; ans += (p-x[tp][i-1])*(int64_t)(y[tp][i]-h); } return ans; }; int tot = 0; int64_t ans = 0; for (int i=0; i<x[0].size(); ++i) { int l = i==0?0:x[0][i-1], r = x[0][i], h = y[0][i]; int posl = get(1,l,r,h); if (posl<0) continue; int posr; for (int j=posl; j<x[1].size(); ++j) { l = x[1][j], r = j+1==x[1].size()?M:x[1][j+1], h = y[1][j+1]; posr = get(0,l,r,h); if (posr!=-1) { ans += calc(1,posl,j+1,y[0][i])-calc(0,i,posr,y[0][i])-(r-x[0][posr])*(int64_t)(h-y[0][i]); break; } } if (posr==-1) break; ++tot; i = posr; } printf("%d %lld ", tot, ans); }
A
题意:给定树,要求给每个点赋一个非负权值$p$,使得对于每个权值为$0$的点$x$,存在一个权值为正的点$v$,满足$p_v$不小于$v$到$x$距离
树形dp套路题,$p$为$0$的点看做白点,否则看做黑点
设${dp}_{x,k,0}$为子树$x$内最远的白点距离$x$为$k$的最小值,${dp}_{x,k,1}$为子树$x$内最近的黑点还能从$x$往上延伸距离$k$的最小值
维护子树大小$sz$
对于${dp}_{x,k,1}$,当$k$超出${sz}_{x}$时也会有转移,但这样${dp}_{x,k,1}=k$,可以前缀优化
对于${dp}_{y,k,1}$,当$k$超出${sz}_{y}$时,同理
其他转移一定都在$sz$范围内,这样复杂度就是$O(n^2)$
#include <bits/stdc++.h> using namespace std; const int N = 5e3+10, INF = 0x3f3f3f3f; int n, sz[N], dp[N][N][2], tmp[N][2], suf[N][2]; vector<int> g[N]; void chkmin(int &a, int b) {a>b?a=b:0;} void dfs(int x, int f) { sz[x] = 1; dp[x][0][0] = 0; for (int u=1; u<=n; ++u) dp[x][u][1] = u; for (int y:g[x]) if (y!=f) { dfs(y,x); for (int u=0; u<=n; ++u) { tmp[u][0] = tmp[u][1] = suf[u][0] = suf[u][1] = INF; } for (int u=0; u<=sz[x]; ++u) { for (int v=0; v<=sz[y]; ++v) { chkmin(tmp[max(u,v+1)][0],dp[x][u][0]+dp[y][v][0]); if (u<=v-1) chkmin(tmp[v-1][1],dp[x][u][0]+dp[y][v][1]); chkmin(tmp[u][0],dp[x][u][0]+dp[y][v][1]); if (u>=v+1) chkmin(tmp[u][1],dp[x][u][1]+dp[y][v][0]); chkmin(tmp[v+1][0],dp[x][u][1]+dp[y][v][0]); chkmin(tmp[max(u,v-1)][1],dp[x][u][1]+dp[y][v][1]); } chkmin(suf[max(sz[y],u)][0],dp[x][u][0]+1); } int mi = suf[0][0]; for (int v=0; v<=n; ++v) { chkmin(mi, suf[v][0]); chkmin(tmp[v][1], mi+v); } for (int v=0; v<=sz[y]; ++v) chkmin(suf[max(sz[x]+1,v+1)][1],dp[y][v][0]); mi = suf[1][1]; for (int u=1; u<=n; ++u) { chkmin(mi, suf[u][1]); chkmin(tmp[u][1], mi+u); } sz[x] += sz[y]; for (int u=0; u<=n; ++u) dp[x][u][0] = tmp[u][0], dp[x][u][1] = tmp[u][1]; for (int u=1; u<=n; ++u) chkmin(dp[x][u][0], dp[x][u-1][0]); for (int u=n-1; u>=0; --u) chkmin(dp[x][u][1], dp[x][u+1][1]); } } int main() { scanf("%d", &n); if (n==1) return puts("1"), 0; for (int i=1; i<n; ++i) { int u, v; scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } memset(dp,0x3f,sizeof dp); dfs(1,0); printf("%d ", dp[1][0][1]); }
CF 1456B
求最小操作数,关键要发现答案一定不大,因为连续三个数二进制最高位相同的话,那么异或后两个数后一定合法,所以$n>60$时答案一定为$1$,否则直接暴力
#include <bits/stdc++.h> using namespace std; const int N = 1e6+10; int n,a[N],s[N]; void brute() { for (int i=1; i<=n; ++i) s[i]=s[i-1]^a[i]; int ans = 1e9; for (int i=1; i<=n; ++i) { for (int j=1; j<i; ++j) { int w = s[i]^s[j-1]; if (j>1&&w<a[j-1]||i<n&&w>a[i+1]) ans = min(ans, i-j); } } for (int l=1; l<=n; ++l) for (int r=l+1; r<=n; ++r) for (int u=r+1; u<=n; ++u) for (int v=u+1; v<=n; ++v) if ((s[r]^s[l-1])>(s[v]^s[u-1])) ans = min(ans, r-l+v-u); if (ans>n) ans = -1; printf("%d ", ans); } int main() { cin>>n; for (int i=1;i<=n;++i) cin>>a[i]; if (n<=60) return brute(),0; puts("1"); }
CF 1456C
能重置$k$次,那么相当于进行$k+1$个独立的周目,然后考虑一种方案的贡献如何算
就相当于是把$n$个数分配到k+1个数组中,假设一个数组分配的数是$x_0,x_1,x_2,x_3,...$,那么贡献就是$x_1+2x_2+3x_3+...$
想让总和最大,考虑贪心,对于负数,想让它前面系数越小越好,对于正数就越大越好
考虑最优解的结构,可以发现,一定是先从小到大逐层铺每个数,然后把剩余数全堆在最高的一列上,枚举这个分界点计算即可
#include <bits/stdc++.h> using namespace std; const int N = 5e5+10; int n, k, a[N]; int64_t sum[N], f[N]; int main() { scanf("%d%d", &n, &k); for (int i=1; i<=n; ++i) scanf("%d", &a[i]); sort(a+1,a+1+n,greater<int>()); for (int i=1; i<=n; ++i) { f[i] = f[i-1]+sum[i-1]; sum[i] = sum[i-1]+a[i]; } int64_t ans = -1e18, ret = 0; int now = 0, cur = 0; for (int i=n; i>=1; --i) { ret += cur*(int64_t)a[i]; ans = max(ans, ret+f[i-1]+(cur+1)*sum[i-1]); if (++now==k+1) ++cur, now = 0; } printf("%lld ", ans); }
CF 1456D
转移有点繁的dp题,关键是要保证最优决策不能漏,花了一个多小时才写完
设${dp}_{i,j}$表示时间为$t_i$时,是否能满足人在位置$x_i$,克隆在位置$x_j$
但是如果直接这样$dp$的话,会发现先在位置$k$放克隆,然后走到位置$k+1$,等待位置$k$蛋糕拿到后,在位置$k+1$放克隆,然后直接往后走,这个时间是没法表示的,所以要在设一个$mi$
${mi}_i$表示当前人在位置$x_{i}$,克隆在位置$x_{i-1}$,等待克隆拿完$i-1$的蛋糕后,把克隆放到位置$i$的最短用时
那么对于${dp}_{i,j}$有三种决策
- 直接走到位置$x_{i+1}$,转移到${dp}_{i+1,j}$,需要满足$t_i+|x_i-x_{i+1}|le t_{i+1}$
- 把克隆放在位置$k$,然后走到位置$i+1$,转移到${dp}_{i+1,k}$,需要满足$t_i+|x_i-x_k|+|x_k-x_{i+1}|le t_{i+1}$
- 把克隆放在位置$i+1$,然后走到位置$i+2$等待放克隆。如果$j=i+1$的话,$i+1$是没必要去的,还要考虑把克隆放在位置$k$,然后走回$i+2$的情况
对于${mi}_i$,${mi}_{i}$转移时首先要满足${mi}_{i}le t_{i}$,有两种决策
- 把克隆放在位置$k$,然后走到位置$i+1$,转移到${dp}_{i+1,k}$
- 走到$i+1$放克隆,转移到${mi}_{i+1}$
#include <bits/stdc++.h> using namespace std; const int N = 5e3+10; int n, t[N], x[N], mi[N]; bool dp[N][N]; int main() { scanf("%d", &n); for (int i=1; i<=n; ++i) scanf("%d%d", &t[i], &x[i]); if (abs(x[1])>t[1]) return puts("NO"),0; for (int i=1; i<=n; ++i) if (abs(x[i])+abs(x[i]-x[1])<=t[1]) dp[1][i] = 1; memset(mi,0x3f,sizeof mi); mi[1] = abs(x[1]); for (int i=1; i<n; ++i) { int f = 1; for (int j=1; j<=n; ++j) { if (!dp[i][j]) continue; if (t[i]+abs(x[i]-x[i+1])<=t[i+1]) dp[i+1][j] = 1; if (f) { for (int k=i+1; k<=n; ++k) if (t[i]+abs(x[i]-x[k])+(int64_t)abs(x[k]-x[i+1])<=t[i+1]) dp[i+1][k] = 1; f = 0; } if (j==i+1) { mi[i+2] = min(mi[i+2], max(t[i+1],t[i]+abs(x[i]-x[i+2]))); for (int k=i+1; k<=n; ++k) if (max(t[i+1],t[i]+abs(x[i]-x[k]))+(int64_t)abs(x[k]-x[i+2])<=t[i+2]) dp[i+2][k] = 1; } else if (t[i]+abs(x[i]-x[i+1])<=t[i+1]) mi[i+2] = min(mi[i+2], (int)max((int64_t)t[i+1],t[i]+abs(x[i]-x[i+1])+(int64_t)abs(x[i+1]-x[i+2]))); } if (mi[i]>t[i]) continue; for (int k=i+1; k<=n; ++k) if (max(t[i],mi[i]+abs(x[k]-x[i]))+(int64_t)abs(x[k]-x[i+1])<=t[i+1]) dp[i+1][k] = 1; if (mi[i]+abs(x[i]-x[i+1])<=t[i+1]) mi[i+1] = min(mi[i+1], max(t[i],mi[i]+abs(x[i]-x[i+1]))); } if (mi[n]<=t[n]||dp[n-1][n]) puts("YES"),exit(0); for (int i=1; i<=n; ++i) if (dp[n][i]) puts("YES"),exit(0); puts("NO"); }