Preface
好难啊这场广州站,不愧是5题金4题铜的超恶劣站,中档题普遍难度较高
但我感觉主要原因还是题目出的太偏向于DP了,AI是本质差不多的树上换根DP,M又是个数位DP,导致像我这种不擅长DP的人直接中期坐牢
但好在祁神大力切出了medium~hard的K题,然后最后一小时我把一直在想的A题丢给徐神后转战I题发现是个傻逼题,过掉后6题收场
最后半小时徐神上去极限写完了A,过了样例后WA了也没时间调了,而且最后坐在下面的时候和祁神又发现了J题的很多性质,但都没时间写了
A. Alice and Her Lost Cat
首先发现\(t_i\)的作用可以最后考虑,因此不妨先考虑树上问题
手玩一下会发现一个叶子节点可以通过直接调监控确定,或者选择通过调查其祖先来确定,而一个子树中最多只能有一个点是通过调查祖先来确定
不妨设\(f_{u,i,0/1}\)表示在点\(u\)的子树内,能确定\(i\)个叶子节点,其中是否存在某个叶子节点是需要让其祖先来确定它
转移的话就是个类似于树上背包的合并,对于不在\(u\)这个点调监控的情况
第三维为\(0\)的状态只能由子树中第三维为\(0\)的状态转移来,而第三维为\(1\)的状态可以由子树中至多一个第三维为\(1\)的状态(其它的都是从\(0\))转移来
而如果要在第三维调监控,那么它的子树的第三维可以在\(0/1\)间选择一个较小的转移,最后再加上\(a_u\)的代价
最后\(ans=\min_\limits{1\le i\le n} (\min(f_{1,i,0},f_{1,i,1})+t_{sz_1-i})\),总复杂度就是树上背包的\(O(n^2)\)
#include
#include
#include
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=2005,INF=5e17;
int T,n,a[N],t[N],x,y,f[N][N][2],g[N][N],sz[N]; vector
inline void DP(CI now=1,CI fa=0)
{
if (v[now].size()==1&&fa)
{
sz[now]=1; f[now][0][0]=f[now][1][1]=0;
f[now][1][0]=a[now]; return;
}
static int tmp[N][2],_tmp[N]; RI i,j;
sz[now]=0; f[now][0][0]=g[now][0]=0;
for (auto to:v[now]) if (to!=fa)
{
for (DP(to,now),i=0;i<=sz[now]+sz[to];++i)
tmp[i][0]=tmp[i][1]=_tmp[i]=INF;
for (i=0;i<=sz[now];++i) for (j=0;j<=sz[to];++j)
{
tmp[i+j][0]=min(tmp[i+j][0],f[now][i][0]+f[to][j][0]);
tmp[i+j][1]=min(tmp[i+j][1],min(f[now][i][1]+f[to][j][0],f[now][i][0]+f[to][j][1]));
_tmp[i+j]=min(_tmp[i+j],g[now][i]+min(f[to][j][0],f[to][j][1]));
}
for (sz[now]+=sz[to],i=0;i<=sz[now];++i)
f[now][i][0]=tmp[i][0],f[now][i][1]=tmp[i][1],g[now][i]=_tmp[i];
}
for (i=0;i<=sz[now];++i) f[now][i][0]=min(f[now][i][0],g[now][i]+a[now]);
}
signed main()
{
//freopen("A.in","r",stdin); freopen("A.out","w",stdout);
for (scanf("%lld",&T);T;--T)
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
for (i=1;i<=n;++i) scanf("%lld",&t[i]),v[i].clear();
for (i=1;i if (n==1) { puts("0"); continue; } int ans=INF; for (DP(),i=1;i<=sz[1];++i) ans=min(ans,min(f[1][i][0],f[1][i][1])+t[sz[1]-i]); printf("%lld\n",ans); } return 0; } B. Ayano and sequences 题目都没看,不可做之数据结构 C. Customs Controls 2 图论构造题,我扔给徐神一个假算法后徐神能还给我一个真算法,真是tql 考虑设\(d_i\)表示\(1\to i\)路径上所有点的点权和,则对于某个点\(v\),它的所有入点\(u_i\)的\(d_{u_i}\)应该都相等,我们可以用并查集维护这些等价关系 然后类似于Tarjan求强连通分量后缩点的过程,我们把\(d_i\)相同的点都缩成一类,不难发现此时得到的还是个DAG 在这个DAG上跑出缩点后每个点从\(1\)出发的最长路\(dis_i\)后,不难发现对于原图中的一个点\(v\)的点权就是\(dis_{\operatorname{getfa(v)}}-dis_{\operatorname{getfa(u)}}\),其中\(u\to v\)为原图中的一条边 本来徐神认为对于一次缩点后的图如果还存在某个点具有超过\(1\)个入点的情况的话就要再次缩点,但我当时觉得这个迭代缩点过程不好写(虽然徐神说很好写)就怂恿他直接交一发 后面也不知道是缩一遍就行了还是数据水了总之就过了,有点难绷 #include constexpr int $n = 200000 + 5; int t, n, m, dis[$n], indu[$n], qu[$n], ans[$n], fa[$n], ars[$n], ql, qr; std::vector inline int father(int p) { if(fa[p] == p) return p; return fa[p] = father(fa[p]); } inline int unn(int a, int b) { a = father(a), b = father(b); if(a != b) fa[b] = a; return a; } int main() { // freopen("1.in", "r", stdin); std::ios::sync_with_stdio(false); std::cin >> t; while(t--) { std::cin >> n >> m; for(int i = 1; i <= n; ++i) ans[i] = indu[i] = ars[i] = dis[i] = 0, fa[i] = i, out[i].clear(), ous[i].clear(); for(int i = 1, f, t; i <= m; ++i) { std::cin >> f >> t; out[f].push_back(t);// back[t].push_back(f); if(ars[t]) ars[t] = unn(ars[t], f); else ars[t] = f; } for(int i = 1; i <= n; ++i) for(auto out: out[i]) { int fr = father(i), to = father(out); if(fr == to) goto __No__; ous[fr].push_back(to); indu[to] += 1; } ql = 0, qr = 0; dis[qu[ql] = father(1)] = 0; while(ql <= qr) { int now = qu[ql++]; for(auto out: ous[now]) { dis[out] = std::max(dis[out], dis[now] + 1); if(--indu[out] == 0) qu[++qr] = out; } } for(int i = 1; i <= n; ++i) if(indu[i]) goto __No__; // std::cerr << "QWQ\n"; for(int i = 1; i <= n; ++i) for(auto out: out[i]) { int f = father(i); int t = father(out); ans[out] = dis[t] - dis[f]; } ans[1] = 1; // for(int i = 1; i <= n; ++i) if(ans[i] <= 0) goto __No__; std::cout << "Yes\n"; for(int i = 1; i <= n; ++i) std::cout << ans[i] << char(i == n ? 10 : 32); continue; __No__: std::cout << "No\n"; } return 0; } D. Digits 题目都没看过 E. Elevator 签到题,将电梯按\(x_i\)从小到大排序后枚举要变成第一名的\(i\),发现要把所有\(x_j\)小于\(x_i\)的电梯都变成\(x_i\)才行 同时由于同时到达看序号的原因还有特别维护下这部分,可以用树状数组实现,代码是祁神写的 #include using namespace std; #define int long long const int N = 5e5+5; int n, m, A[N], id[N]; int ans[N]; int cnt[N]; int lb(int x){return x&(-x);} void upd(int pos){ for (int i=pos; i<=n; i+=lb(i)){ ++cnt[i]; } } int qry(int pos){ int res=0; for (int i=pos; i>0; i-=lb(i)) res+=cnt[i]; return res; } bool cmp(int a, int b){