NOIP2024加赛6
NOIP2024加赛6
\(T1\) P323. 草莓 \(60pts\)
-
部分分
- \(60pts\)
- 先将 \(\{ x \},\{ y \}\) 降序排序,状态转移方程为 \(f_{i,j}=\min(f_{i-1,j}+x_{i}(j+1),f_{i,j-1}+y_{j}(i+1))\) ,边界为 \(f_{0,0}=0\) ,最终有 \(f_{n-1,m-1}\) 即为所求。
- 若费用提前计算则需要将 \(\{ x \},\{ y \}\) 升序排序并做前缀和,状态转移方程为 \(f_{i,j}=\min(f_{i-1,j}+sumx_{i},f_{i,j-1}+sumy_{j})\) ,边界为 \(f_{0,0=0}\) ,最终有 \(f_{n-1,m-1}+sumx_{n-1}+sumy_{m-1}\) 即为所求。
点击查看代码
ll x[200010],y[200010],f[2][200010]; int main() { #define Isaac #ifdef Isaac freopen("guiltiness.in","r",stdin); freopen("guiltiness.out","w",stdout); #endif ll n,m,i,j; cin>>n>>m; n--; m--; for(i=1;i<=n;i++) { cin>>x[i]; } for(i=1;i<=m;i++) { cin>>y[i]; } sort(x+1,x+n+1); sort(y+1,y+m+1); for(i=1;i<=n;i++) { x[i]+=x[i-1]; } for(i=1;i<=m;i++) { y[i]+=y[i-1]; } memset(f,0x3f,sizeof(f)); f[0][0]=0; for(i=0;i<=n;i++) { for(j=0;j<=m;j++) { f[(i+1)&1][j]=0x3f3f3f3f3f3f3f3f; } for(j=0;j<=m;j++) { f[(i+1)&1][j]=min(f[(i+1)&1][j],f[i&1][j]+y[j]); f[i&1][j+1]=min(f[i&1][j+1],f[i&1][j]+x[i]); } } cout<<f[n&1][m]+x[n]+y[m]<<endl; return 0; }
- \(60pts\)
-
正解
- 上述做法因为需要求解在网格图上的最短路,只能另寻他法。
- 考虑临项交换,观察到最终操作序列上相邻的 \(x_{i},y_{j}\) 交换成 \(y_{j},x_{i}\) 更优当且仅当 \(y_{j}>x_{i}\) ,将 \(\{ x \},\{ y \}\) 放到一起降序排序即可。
点击查看代码
ll cnt[2]; vector<pair<ll,ll> >a; int main() { #define Isaac #ifdef Isaac freopen("guiltiness.in","r",stdin); freopen("guiltiness.out","w",stdout); #endif ll n,m,x,ans=0,i; cin>>n>>m; n--; m--; for(i=1;i<=n+m;i++) { cin>>x; a.push_back(make_pair(x,i>n)); } sort(a.begin(),a.end()); reverse(a.begin(),a.end()); for(i=0;i<a.size();i++) { ans+=a[i].first*(cnt[a[i].second^1]+1); cnt[a[i].second]++; } cout<<ans<<endl; return 0; }
\(T2\) P324. 三色 \(0pts\)
-
部分分
- 子任务 \(1,2\)
- 类似 luogu P11233 [CSP-S 2024] 染色 ,设 \(f_{i,j,k}\) 表示 \([1,i]\) 中第一个与 \(a_{i}\) 颜色不同的是 \(a_{j}\) ,第一个与 \(a_{i},a_{j}\) 颜色互不相同的数是 \(a_{k}\) 的方案数。转移时分讨 \(a_{i+1}=a_{i}/a_{j}/a_{k}\) 刷表法转移即可。边界为 \(f_{1,0,0}=3\) 。
- 当访问到一个限制时及时去除不合法状态。
- 最终有 \(\sum\limits_{j=0}^{n-1}\sum\limits_{k=0}^{\max(0,j-1)}f_{n,j,k}\) 即为所求。
- 时间复杂度为 \(O(n^{3}+m)\) 。
点击查看代码
const int p=1000000007; int f[2][5010][5010]; vector<pair<int,int> >q[5010]; int main() { #define Isaac #ifdef Isaac freopen("color.in","r",stdin); freopen("color.out","w",stdout); #endif int t,n,m,l,r,x,ans,lj,lk,rj,rk,i,j,k,h,v; scanf("%d",&t); for(v=1;v<=t;v++) { ans=0; cin>>n>>m; for(i=1;i<=n;i++) { q[i].clear(); } for(i=1;i<=m;i++) { scanf("%d%d%d",&l,&r,&x); q[r].push_back(make_pair(l,x)); } memset(f,0,sizeof(f)); f[1][0][0]=3; for(i=1;i<=n;i++) { lj=lk=0; rj=rk=0x3f3f3f3f; for(j=0;j<q[i].size();j++) { switch(q[i][j].second) { case 1: rj=min(rj,q[i][j].first-1); break; case 2: lj=max(lj,q[i][j].first); rk=min(rk,q[i][j].first-1); break; case 3: lk=max(lk,q[i][j].first); break; default: break; } } for(j=0;j<=i;j++) { for(k=0;k<=max(0,j-1);k++) { if(j<lj||j>rj||k<lk||k>rk) { f[i&1][j][k]=0; } f[(i+1)&1][j][k]=0; } } for(j=0;j<=i-1;j++) { for(k=0;k<=max(0,j-1);k++) { f[(i+1)&1][j][k]=(f[(i+1)&1][j][k]+f[i&1][j][k])%p; f[(i+1)&1][i][k]=(f[(i+1)&1][i][k]+f[i&1][j][k])%p; f[(i+1)&1][i][j]=(f[(i+1)&1][i][j]+f[i&1][j][k])%p; } } } for(j=0;j<=n-1;j++) { for(k=0;k<=max(0,j-1);k++) { ans=(ans+f[n&1][j][k])%p; } } cout<<ans<<endl; } return 0; }
- 子任务 \(1,2\)
-
正解
- 仍考虑 luogu P11233 [CSP-S 2024] 染色 的优化方式,将 \(j,k\) 看做矩阵。当 \(i\) 转移到 \(i+1\) 时,分讨 \(a_{i+1}=a_{i}/a_{j}/a_{k}\) 分别对应复制到 \(i+1\) 处/将每列累加到 \(i+1\) 行的对应位置/将每行累加到 \(i+1\) 列的对应位置。前者直接滚动数组继承,后两者可以看做新加入一行/列。考虑维护每一行、每一列的前缀和。
- 遇到限制时等价于保留合法矩阵,将不合法状态置零。
- 发现处理完限制后保留的都是一个区间,使用
deque
维护合法状态即可,使得每个数仅被加入、删除 \(O(1)\) 次。 - 时间复杂度为 \(O(n^{2}+m)\) 。
点击查看代码
const int p=1000000007; int s[5010],lj[5010],rj[5010],lk[5010],rk[5010],sh[5010],sl[5010],f[5010][5010]; deque<int>q[5010]; void del(int j,int l,int r) { while(q[j].empty()==0&&q[j].front()<l) { sh[j]=(sh[j]-f[j][q[j].front()]+p)%p; sl[q[j].front()]=(sl[q[j].front()]-f[j][q[j].front()]+p)%p; q[j].pop_front(); } while(q[j].empty()==0&&q[j].back()>r) { sh[j]=(sh[j]-f[j][q[j].back()]+p)%p; sl[q[j].back()]=(sl[q[j].back()]-f[j][q[j].back()]+p)%p; q[j].pop_back(); } } int main() { #define Isaac #ifdef Isaac freopen("color.in","r",stdin); freopen("color.out","w",stdout); #endif int t,n,m,l,r,x,ans,i,j,k,v; scanf("%d",&t); for(v=1;v<=t;v++) { scanf("%d%d",&n,&m); ans=0; for(i=0;i<=n;i++) { lj[i]=lk[i]=0; rj[i]=rk[i]=i-1; sh[i]=sl[i]=0; q[i].clear(); for(j=0;j<=max(0,i-1);j++) { q[i].push_back(j); } for(j=0;j<=n;j++) { f[i][j]=0; } } for(i=1;i<=m;i++) { scanf("%d%d%d",&l,&r,&x); switch(x) { case 1: rj[r]=min(rj[r],l-1); break; case 2: lj[r]=max(lj[r],l); rk[r]=min(rk[r],l-1); break; case 3: lk[r]=max(lk[r],l); break; default: break; } } f[0][0]=sh[0]=sl[0]=3; for(i=1;i<=n;i++) { for(j=0;j<=i-1;j++) { if(lj[i]<=j&&j<=rj[i]) { del(j,lk[i],rk[i]); } else { del(j,j+1,0);//全部清空 } } if(i!=n) { for(k=0;k<=i-1;k++) { f[i][k]=(sh[k]+sl[k])%p; sh[i]=(sh[i]+f[i][k])%p; sl[k]=(sl[k]+f[i][k])%p; } } } for(i=0;i<=n;i++) { ans=(ans+sh[i])%p; } printf("%d\n",ans); } return 0; }
\(T3\) P325. 博弈 \(0pts\)
-
选出的三个数为 \(a,b,c(a<b<c)\) 时,有
Madeline
必胜。- 若 \(a,c\) 关于 \(b\) 对称,那么
Madeline
直接操作 \(a,c\) 使其等于 \(b\) 即可获胜。 - 否则,以 \(a'=a-(c-b)>0,b,b\) 为例( \(b,b,c'=c-(b-a)>0\) 同理)。若在进入该状态后
Badeline
必胜,则Madeline
直接仿照原Badeline
的操作方式操作(多操作几步使其到达Badeline
必胜的第一步情况)即可获胜;否则因为Badeline
必败,故Madeline
必胜。
- 若 \(a,c\) 关于 \(b\) 对称,那么
-
选出的三个数中有两个数相同时,以 \(a,a,b(a<b)\) 为例。此时
Madeline
只能将 \(a,b\) 操作成 \(\frac{a+b}{2}\) (如果无法操作必败),Badeline
继续这种操作方式。若 \(|a-b|\) 最低位的 \(1\) 是第偶数位Madeline
必胜,即__builtin_ffs(abs(a-b))%2==0
。 -
选出的三个数都相同时
Madeline
必败。 -
将 \(\{ a \}\) 倒着插入 \(01Trie\) 树上统计叶子个数即可。
点击查看代码
unordered_map<ll,ll>vis; unordered_map<ll,ll>::iterator it; struct Trie { int son[30000010][4],siz[30000010],rt_sum=1; void clear() { for(int i=1;i<=rt_sum;i++) { son[i][0]=son[i][1]=son[i][2]=son[i][3]=siz[i]=0; } rt_sum=1; } int build_rt() { rt_sum++; return rt_sum; } void insert(ll s) { int x=1; for(int i=0;i<=60;i+=2) { if(son[x][(s>>i)&3]==0) { son[x][(s>>i)&3]=build_rt(); } siz[x]++; x=son[x][(s>>i)&3]; } siz[x]++; } ll query(ll s) { int x=1; ll ans=0; for(int i=0;i<=60;i+=2) { ans+=(siz[son[x][((s>>i)&3)^1]]+siz[son[x][((s>>i)&3)^3]]); x=son[x][(s>>i)&3]; } return ans; } }T; int main() { #define Isaac #ifdef Isaac freopen("game.in","r",stdin); freopen("game.out","w",stdout); #endif ll t,n,x,ans,i,j; scanf("%lld",&t); for(j=1;j<=t;j++) { T.clear(); vis.clear(); scanf("%lld",&n); ans=n*(n-1)*(n-2)/6; for(i=1;i<=n;i++) { scanf("%lld",&x); T.insert(x); vis[x]++; } for(it=vis.begin();it!=vis.end();it++) { ans-=it->second*(it->second-1)/2*T.query(it->first); ans-=it->second*(it->second-1)*(it->second-2)/6; } printf("%lld\n",ans); } return 0; }
\(T4\) P326. 后缀数组 \(0pts\)
-
原题: CodeChef TASUFFIX-Very Long Suffix Array
点击查看 std 代码
#include<iostream> #include<stdio.h> #include<ctype.h> #include<random> #include<map> #include<math.h> #define ll long long #define ld long double #define fi first #define se second #define pii pair<int,int> #define lowbit(x) ((x)&-(x)) #define popcount(x) __builtin_popcount(x) #define inf 0x3f3f3f3f #define infll 0x3f3f3f3f3f3f3f3f #define umap unordered_map #define all(x) x.begin(),x.end() #define mk make_pair #define pb push_back #define ckmax(x,y) x=max(x,y) #define ckmin(x,y) x=min(x,y) #define rep(i,l,r) for(int i=l;i<=r;++i) #define per(i,r,l) for(int i=r;i>=l;--i) #define N 100005 using namespace std; inline int read(){ int x=0,f=0; char ch=getchar(); while(!isdigit(ch)) f|=(ch==45),ch=getchar(); while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); return f?-x:x; } const int mo=998244353; inline void red(int &x){x>=mo?x-=mo:0;} inline int qpow(int x,int b){ int res=1; for(;b;x=1LL*x*x%mo,b>>=1) if(b&1) res=1LL*res*x%mo; return res; } struct FHQ{ int ls,rs,sze,cnt,sum,lazy; pii v; }T[N*2]; int rt,pool; mt19937 rnd(114514); inline int newNode(int l,int r){ T[++pool].sze=1,T[pool].cnt=T[pool].sum=max(r-l+1,l-r+1),T[pool].v={l,r}; return pool; } inline void pushup(int k){ T[k].sze=T[T[k].ls].sze+T[T[k].rs].sze+1; T[k].sum=T[T[k].ls].sum+T[T[k].rs].sum+T[k].cnt; } inline void pushdown(int k){ if(!T[k].lazy) return; T[k].lazy=0; swap(T[T[k].ls].v.fi,T[T[k].ls].v.se); swap(T[T[k].rs].v.fi,T[T[k].rs].v.se); swap(T[T[k].ls].ls,T[T[k].ls].rs); swap(T[T[k].rs].ls,T[T[k].rs].rs); T[T[k].ls].lazy^=1; T[T[k].rs].lazy^=1; } int U; void split(int k,int &x,int &y,int s){ if(!k) return (void)(x=y=0); pushdown(k); if(T[T[k].ls].sum+T[k].cnt<=s){ x=k; split(T[k].rs,T[k].rs,y,s-T[T[k].ls].sum-T[k].cnt); U+=T[T[k].ls].sum+T[k].cnt; } else{ y=k; split(T[k].ls,x,T[k].ls,s); } pushup(k); } int merge(int x,int y){ if(!x || !y) return x+y; pushdown(x),pushdown(y); if(rnd()%(T[x].sze+T[y].sze)<T[x].sze){ T[x].rs=merge(T[x].rs,y); pushup(x);return x; } else{ T[y].ls=merge(x,T[y].ls); pushup(y);return y; } } int n,m; pii b[2*N]; void get(int k){ pushdown(k); if(T[k].ls) get(T[k].ls); b[++m]=T[k].v; if(T[k].rs) get(T[k].rs); } map<int,int> rk; signed main(){ freopen("sa.in","r",stdin); freopen("sa.out","w",stdout); n=read(),m=read(); rt=newNode(1,n); for(int i=1;i<=m;++i){ int op=read(),l=read(),r=read(); int x,y,z; U=0; split(rt,x,y,l-1); U=l-1-U; if(U){ int now=y; while(T[now].ls){ pushdown(now); T[now].sum-=U; now=T[now].ls; } T[now].sum-=U,T[now].cnt-=U; if(T[now].v.fi<=T[now].v.se){ int mid=T[now].v.fi+U-1; x=merge(x,newNode(T[now].v.fi,mid)); T[now].v.fi=mid+1; } else{ int mid=T[now].v.fi-U+1; x=merge(x,newNode(T[now].v.fi,mid)); T[now].v.fi=mid-1; } } U=0; split(y,y,z,r-l+1); U=(r-l+1)-U; if(U){ int now=z; while(T[now].ls){ pushdown(now); T[now].sum-=U; now=T[now].ls; } T[now].sum-=U,T[now].cnt-=U; if(T[now].v.fi<=T[now].v.se){ int mid=T[now].v.fi+U-1; y=merge(y,newNode(T[now].v.fi,mid)); T[now].v.fi=mid+1; } else{ int mid=T[now].v.fi-U+1; y=merge(y,newNode(T[now].v.fi,mid)); T[now].v.fi=mid-1; } } if(op==0){ rt=merge(y,merge(x,z)); } else{ swap(T[y].v.fi,T[y].v.se); swap(T[y].ls,T[y].rs); T[y].lazy^=1; rt=merge(x,merge(y,z)); } } m=0; get(rt); // for(int i=1;i<=m;++i) fprintf(stderr,"%d %d\n",b[i].fi,b[i].se); int pre=0; for(int i=1;i<=m;++i){ rk[b[i].fi]=pre+1; pre+=abs(b[i].fi-b[i].se)+1; rk[b[i].se]=pre; } int ans=-1,lst=-1; pre=0; for(int i=1;i<=m;++i){ if(b[i].fi<=b[i].se){ if(b[i].fi!=b[i].se){ if(pre+2>lst) ans++; } else if(rk[b[i].fi+1]>lst) ans++; pre+=b[i].se-b[i].fi+1; if(b[i].fi!=b[i].se){ if(rk[b[i].se+1]>pre) ans++; } lst=rk[b[i].se+1]; } else{ if(rk[b[i].fi+1]>lst) ans++; if(pre+1>rk[b[i].fi+1]) ans++; pre+=b[i].fi-b[i].se+1; lst=pre-1; } ans+=max(0,abs(b[i].fi-b[i].se)-1); } cout<<qpow(2,ans); return 0; }
总结
- \(T1\) 口胡的费用提前计算一开始想假了,将横竖的边权搞反了。
后记
- \(T3\) 题面过于狗屎,没有说明行动过程中改变之后的数值仍为整数。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18553208,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。