第一题是鸽子固定器。
只会10分。按 s 从小到大排序,然后 dp[ i ][ j ][ k ] 表示前 i 个元素、已经选了 j 个、最小值所在位置是 k 的最大代价。
#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } ll Mx(ll a,ll b){return a>b?a:b;} ll Mn(ll a,ll b){return a<b?a:b;} const int N=1005,M=55; int n,m,ds,dv,f[M][N]; ll ans=-1e18; struct Node{ int s,v; bool operator< (const Node &b)const {return s<b.s;} }a[N]; ll calv(int x) { if(dv==1)return x;return (ll)x*x;} ll cals(int x) { if(ds==1)return x;return (ll)x*x;} int main() { n=rdn();m=rdn();ds=rdn();dv=rdn(); for(int i=1;i<=n;i++) a[i].s=rdn(),a[i].v=rdn(); sort(a+1,a+n+1); for(int i=1;i<=n;i++) { for(int j=Mn(i,m);j>1;j--) for(int k=1;k<i;k++) { ans=Mx(ans,calv(f[j-1][k]+a[i].v)-cals(a[i].s-a[k].s)); f[j][k]=Mx(f[j][k],f[j-1][k]+a[i].v); } f[1][i]=a[i].v; ans=Mx(ans,calv(a[i].v)); } printf("%lld ",ans); return 0; }
当 ds=dv=1 的时候,不用考虑最小值是多少,只要考虑新增一个元素的代价。也就是原来的最后一个元素不是最大值、自己才是最大值。
那么 ( dp[i][j] ) 表示前 i 个选 j 个、一定选了第 i 个;这样就知道第 i 个是当前的最后一个。
转移只需 ( dp[i][j]=v[i]+s[i]+maxlimits_{1<=k<i} dp[k][j-1]-s[k] ) ;前缀最大值优化 DP 。没有实现。
应该考虑不仅用 DP 来做。
注意到一个暴力做法:确定 r ,枚举 l ;那么中间选择的一定是 v 最大的一些元素。那么维护对于 v 的小根堆,看看 l 能否取代堆顶即可。
1.一旦 r 被弹出堆,就直接枚举下一个 r ; 2.用 ST 表来二分寻找下一个 l (下一个 v 大于堆顶的元素)。
根据题解的证明,这样复杂度是 O(nmlogn) ,可过。虽然还可以做到 O(nm) ,但没有实现。
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define ll long long using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } ll Mx(ll a,ll b){return a>b?a:b;} ll Mn(ll a,ll b){return a<b?a:b;} const int N=2e5+5,K=20; int n,m,ds,dv,lg[N],bin[K],mx[N][K]; ll ans; struct Node{ int s,v; bool operator< (const Node &b)const {return s<b.s;} }a[N]; priority_queue<int,vector<int>,greater<int> > q; ll cal(int v,int s){return (dv==1?v:(ll)v*v)-(ds==1?s:(ll)s*s);} int main() { n=rdn();m=rdn();ds=rdn();dv=rdn(); for(int i=1;i<=n;i++) a[i].s=rdn(), a[i].v=rdn(); for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1; bin[0]=1;for(int i=1;i<=lg[n]+1;i++)bin[i]=bin[i-1]<<1;//+1 sort(a+1,a+n+1); for(int i=1;i<=n;i++) { mx[i][0]=a[i].v; for(int t=1;i>=bin[t];t++) mx[i][t]=Mx(mx[i][t-1],mx[i-bin[t-1]][t-1]); } for(int i=1,j;i<=n;i++) { while(q.size())q.pop(); int sm=0; for(j=i;j&&j>i-m;j--) { q.push(a[j].v);sm+=a[j].v; ans=Mx(ans,cal(sm,a[i].s-a[j].s)); } while(1) { int d=q.top(); if(d==a[i].v)break; for(int t=lg[j];t>=0;t--) if(bin[t]<=j&&mx[j][t]<=d)j-=bin[t];//bin[t]<=j if(!j)break; q.pop(); q.push(a[j].v); sm+=a[j].v-d; ans=Mx(ans,cal(sm,a[i].s-a[j].s)); j--;///// } } printf("%lld ",ans); return 0; }
第二题是 To Do Tree 。
每次把新解锁的点放进堆里,每次操作取出向下的 dep 前 m 大的元素即可。不太会证明……
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } int Mx(int a,int b){return a>b?a:b;} int Mn(int a,int b){return a<b?a:b;} const int N=1e5+5; int n,m,hd[N],xnt,to[N<<1],nxt[N<<1]; int dep[N],p[N],tot,ct[N],cnt; struct cmp{ bool operator() (const int &a,const int &b)const {return dep[a]<dep[b];} }; priority_queue<int,vector<int>,cmp> q; void add(int x,int y) { to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt; } void dfs(int cr) { for(int i=hd[cr],v;i;i=nxt[i]) { dfs(v=to[i]); dep[cr]=Mx(dep[cr],dep[v]+1); } } int main() { n=rdn();m=rdn(); for(int i=2,d;i<=n;i++) { d=rdn();add(d,i);} dfs(1); q.push(1); while(q.size()) { cnt++; int lst=tot; while(q.size()) { p[++tot]=q.top(); q.pop(); if(tot-lst==m)break; } ct[cnt]=tot-lst; for(int i=lst+1;i<=tot;i++) for(int j=hd[p[i]];j;j=nxt[j]) q.push(to[j]); } printf("%d ",cnt); for(int i=1,nw=1;i<=cnt;i++) { printf("%d ",ct[i]); for(int j=1;j<=ct[i];j++,nw++) printf("%d ",p[nw]); puts(""); } return 0; }
第三题是配对树。
不知道怎么不枚举序列上的区间。
考虑 m2 枚举区间。把区间里的元素“加入”表示树上该点到根的链上点的状态都取反。树点的状态为 0/1 表示其子树里有 偶数/奇数 个区间里的点;如果状态是 1 的话,该点连向父亲的边需要加入答案。
用 LCT 维护这个过程。区间 (L-2,R) 可以继承 (L,R) 的状态。应该是 O( m2logn ) 的,但是在链的数据上实测很慢。不 makeroot 而每次 access 的 LCT 复杂度?总之得了20分。
#include<cstdio> #include<cstring> #include<algorithm> #define ll long long #define ls c[x][0] #define rs c[x][1] using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } const int N=1e5+5,mod=998244353; int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;} int n,m,a[N],hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1],ans,prn; int fa[N],c[N][2],vl[N],sm[N],s2[N]; bool tg[N],b[N]; bool rv[N]; int sta[N],top; void add(int x,int y,int z) { to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;} void dfs(int cr,int f) { fa[cr]=f; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=f) { vl[v]=sm[v]=w[i]; dfs(v,cr); } } bool isrt(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;} void pshd(int x) { if(rv[x]) { rv[x]=0; if(ls)rv[ls]^=1; if(rs)rv[rs]^=1; swap(ls,rs); } if(!tg[x])return; tg[x]=0; if(ls) s2[ls]=upt(sm[ls]-s2[ls]), tg[ls]^=1, b[ls]^=1; if(rs) s2[rs]=upt(sm[rs]-s2[rs]), tg[rs]^=1, b[rs]^=1; } void pshp(int x) { sm[x]=upt(sm[ls]+sm[rs]); sm[x]=upt(sm[x]+vl[x]); s2[x]=upt(s2[ls]+s2[rs]); if(b[x])s2[x]=upt(s2[x]+vl[x]); } void rotate(int x) { int y=fa[x],z=fa[y]; bool d=(x==c[y][1]); if(!isrt(y))c[z][y==c[z][1]]=x; fa[x]=z; fa[y]=x; fa[c[x][!d]]=y; c[y][d]=c[x][!d]; c[x][!d]=y; pshp(y); pshp(x); } void splay(int x) { sta[top=1]=x; for(int k=x;fa[k];k=fa[k])sta[++top]=fa[k]; for(int i=top;i;i--)pshd(sta[i]); for(int y,z;!isrt(x);rotate(x)) { y=fa[x];z=fa[y]; if(!isrt(y)) ((x==c[y][0])^(y==c[z][0]))?rotate(x):rotate(y); } } void access(int x) { for(int t=0;x;t=x,x=fa[x]) { splay(x);//tg[x]=0 c[x][1]=t; pshp(x); } } void mkrt(int x) { access(x); splay(x); rv[x]^=1; swap(ls,rs); } void cz(int x) { if(rand()&1) { mkrt(1); access(x); splay(x); int y=s2[x]; s2[x]=upt(sm[x]-s2[x]); tg[x]^=1; b[x]^=1; ans=upt(ans+s2[x]-y); } else { mkrt(x); access(1); splay(1); int y=s2[1]; s2[1]=upt(sm[1]-s2[1]); tg[1]^=1; b[1]^=1; ans=upt(ans+s2[1]-y); } } int main() { srand(1039121); n=rdn();m=rdn(); for(int i=1,u,v,w;i<n;i++) { u=rdn();v=rdn();w=rdn()%mod; add(u,v,w); add(v,u,w); } for(int i=1;i<=m;i++)a[i]=rdn(); dfs(1,0); for(int i=2;i<=m;i++) { for(int j=i;j>1;j-=2) { cz(a[j]); cz(a[j-1]); prn=upt(prn+ans); } ans=0; for(int j=1;j<=n;j++)s2[j]=0, tg[j]=b[j]=0; } printf("%d ",prn); return 0; }
不枚举序列上的区间的方法就是直接考虑一条树边被算了几次。
把一个点的子树里的节点对应的序列上的位置都赋值为1。有多少个 “长度为偶数且包含了奇数个1” 的区间,该点连向父亲的边就被算了多少次。
因为是把子树里的点的位置都赋值为1,所以考虑线段树合并。
又注意到长度为偶数的区间 ( L, R ) ,R 和 L-1 的奇偶性相同;所以分开考虑序列的奇数位置和偶数位置,维护“前缀和为奇数/偶数”的个数即可。区间合并的时候看左区间若有奇数个1,就需要把右区间的奇/偶翻转再加到自己身上。
注意动态开点的话,pshp 的时候小心没有左/右孩子的情况。
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #define ll long long #define ls Ls[cr] #define rs Rs[cr] using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } const int N=1e5+5,M=N*20,mod=998244353; int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;} void Add(int &x,int y){x=upt(x+y);} int n,m,hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1],ans; int rt[N],tot,Ls[M],Rs[M],sm[M],ct[M][2]; vector<int> ps[N]; void add(int x,int y,int z) { to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;} int cal(bool fx,int l,int r) { if(!fx)return (r>>1)-((l-1)>>1);//if l==0 then +1 return ((r+1)>>1)-(l>>1); } void pshp(int cr,int l,int mid,int r) { sm[cr]=sm[ls]+sm[rs]; for(int i=0;i<=1;i++)ct[cr][i]=ct[ls][i]; if(sm[ls]&1) for(int i=0;i<=1;i++) Add(ct[cr][i],cal(i,mid+1,r)-ct[rs][i]);//-ct... else for(int i=0;i<=1;i++) Add(ct[cr][i],ct[rs][i]); } void mrg(int l,int r,int &cr,int pr) { if(!cr){cr=pr;return;} if(!pr)return; int mid=l+r>>1; mrg(l,mid,ls,Ls[pr]); mrg(mid+1,r,rs,Rs[pr]); pshp(cr,l,mid,r); } void mdfy(int l,int r,int &cr,int p) { if(!cr) cr=++tot; if(l==r) { if(l&1)ct[cr][1]++; else ct[cr][0]++; sm[cr]=1; return; } int mid=l+r>>1; if(p<=mid)mdfy(l,mid,ls,p); else mdfy(mid+1,r,rs,p); pshp(cr,l,mid,r); } void dfs(int cr,int fa,int tw) { for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=fa) { dfs(v,cr,w[i]); mrg(0,m,rt[cr],rt[v]); } for(int i=0,lm=ps[cr].size();i<lm;i++) mdfy(0,m,rt[cr],ps[cr][i]); int r=rt[cr]; ans=(ans+(ll)ct[r][0]*(cal(0,0,m)-ct[r][0])%mod*tw)%mod; ans=(ans+(ll)ct[r][1]*(cal(1,0,m)-ct[r][1])%mod*tw)%mod; } int main() { n=rdn();m=rdn(); for(int i=1,u,v,z;i<n;i++) { u=rdn();v=rdn();z=rdn(); add(u,v,z);add(v,u,z); } for(int i=1,d;i<=m;i++) { d=rdn();ps[d].push_back(i);} dfs(1,0,0); printf("%d ",ans); return 0; }