NOIP2024(欢乐)加赛3
NOIP2024(欢乐)加赛3
\(T1\) CF2033B Sakurako and Water \(100pts\)
-
枚举主对角线取 \(\max\) 即可。
点击查看代码
ll a[510][510]; int main() { ll t,n,ans=0,maxx,i,j,k,h; cin>>t; for(h=1;h<=t;h++) { cin>>n; ans=0; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { cin>>a[i][j]; } } for(k=-n+1;k<=n+1;k++) { maxx=0; for(i=1,j=i+k;i<=n;i++,j++) { if(i<=n&&1<=j&&j<=n&&a[i][j]<0) { maxx=max(maxx,-a[i][j]); } } ans+=maxx; } cout<<ans<<endl; } return 0; }
\(T2\) CF2025B Binomial Coefficients, Kind Of \(100pts\)
-
观察样例加打表可知, \(2^{[k \ne n] \times k}\) 即为所求。
- 已知递推式 \(f_{i,j}=\begin{cases} 1 & j=0 \lor j=n \\ f_{n,k-1}+f_{n-1,k-1} & j \in [1,n) \end{cases}\) ,省去 \(j=n\) 的状态后有 \(f_{i,j}=f_{i+1,j}\) 。
- \(f_{i,j}\) 能更新到的值只有 \(\begin{cases} f_{i,j+1}=f_{i+1,j+1}=2f_{i,j} \end{cases}\) ,故 \(2^{[k \ne n] \times k}\) 即为所求。
点击查看代码
const ll p=1000000007; ll n[100010],m[100010],C[1010][1010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { ll t,i; cin>>t; for(i=1;i<=t;i++) { cin>>n[i]; } for(i=1;i<=t;i++) { cin>>m[i]; if(m[i]==n[i]) { cout<<1<<endl; } else { cout<<qpow(2,m[i],p)<<endl; } } return 0; }
\(T3\) CF2030D QED's Favorite Permutation \(100pts\)
-
对于移动,我们可以无脑进行交换来保证移动,然后将中途交换的位置再交换回去。
-
通过手摸不难发现, \(p_{i}\) 能移动到 \(i\) 当且仅当 \(s_{\min(i,p_{i}) \sim \max(i,p_{i})}\) 中不含有
LR
子串。 -
反向考虑,即
LR
子串不被上述区间包含,差分判断即可。点击查看代码
ll a[200010],d[200010]; char s[200010]; int main() { ll t,n,m,x,sum,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n>>m; sum=0; for(i=1;i<=n;i++) { cin>>a[i]; d[min(a[i],i)]++; d[max(a[i],i)]--; } for(i=1;i<=n;i++) { d[i]+=d[i-1]; } cin>>(s+1); for(i=1;i<=n-1;i++) { if(s[i]=='L'&&s[i+1]=='R') { sum+=(d[i]!=0); } } for(i=1;i<=m;i++) { cin>>x; if(s[x]=='L') { s[x]='R'; if(x+1<=n&&s[x+1]=='R') { sum-=(d[x]!=0); } if(x-1>=1&&s[x-1]=='L') { sum+=(d[x-1]!=0); } } else { s[x]='L'; if(x+1<=n&&s[x+1]=='R') { sum+=(d[x]!=0); } if(x-1>=1&&s[x-1]=='L') { sum-=(d[x-1]!=0); } } if(sum!=0) { cout<<"No"<<endl; } else { cout<<"Yes"<<endl; } } for(i=1;i<=n;i++) { d[i]=0; } } return 0; }
\(T4\) CF2025E Card Game \(10pts\)
-
部分分
- \(10pts\) :输出样例。
-
正解
- 对于除了花色为 \(1\) 的卡牌中,
Alice
选择的卡牌数量必须 \(\le\)Bob
选择的卡牌数量。即若设 \(s_{i,0/1}\) 表示花色为 \(i\) 的卡牌Alice
/Bob
选择的卡牌数量,则有 \(s_{1,0}-s_{1,1}=\sum\limits_{i=2}^{n}s_{i,1}-s_{i,0}(s_{i,1}-s_{i,0} \ge 0)\) 。 - 设 \(f_{i,j}\) 表示同种花色,前 \(i\) 大等级的卡牌中
Alice
多拿了 \(j\) 张牌且双方都没有多余的牌的方案数,状态转移方程为 \(f_{i,j}=[j-1 \ge 0] \times f_{i-1,j-1}+[j+1 \le m] \times f_{i-1,j+1}\) 。边界为 \(f_{0,0}=1\) 。 - 设 \(g_{i,j}\) 表示 \([2,i]\) 花色中
Bob
一共多拿了 \(j\) 张牌且双方都没有多余牌的方案数,状态转移方程为 \(g_{i,j}=\sum\limits_{k=0}^{j}g_{i-1,k} \times f_{m,j-k}\) ,边界为 \(g_{1,0}=1\) 。 - 最终,有 \(\sum\limits_{i=0}^{m}g_{n,i} \times f_{m,i}\) 即为所求,可以使用矩阵快速幂加速。
点击查看代码
const ll p=998244353; ll f[2][510],g[2][510]; int main() { // #define Issac #ifdef Issac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,ans=0,i,j,k; cin>>n>>m; f[0][0]=1; for(i=1;i<=m;i++) { for(j=0;j<=i;j++) { f[i&1][j]=f[(i-1)&1][j+1]; if(j-1>=0) { f[i&1][j]=(f[i&1][j]+f[(i-1)&1][j-1])%p; } } } g[1][0]=1; for(i=2;i<=n;i++) { for(j=0;j<=m;j++) { g[i&1][j]=0; for(k=0;k<=j;k++) { g[i&1][j]=(g[i&1][j]+g[(i-1)&1][k]*f[m&1][j-k]%p)%p; } } } for(i=0;i<=m;i++) { ans=(ans+f[m&1][i]*g[n&1][i]%p)%p; } cout<<ans<<endl; return 0; }
- 对于除了花色为 \(1\) 的卡牌中,
\(T5\) CF1967D Long Way to be Non-decreasing \(16pts\)
-
部分分
- \(16pts\)
- 考虑从 \(i\) 向 \(b_{i}\) 连一条有向边,那么 \(i\) 能到达的点就是经过若干次操作后可以成为的值。
- 答案上界显然为 \(m\) ,考虑二分答案。
- \(check\) 时求出令 \(a_{i}\) 走至多 \(mid\) 步能到达的范围内不小于 \(a_{i-1}'\) 的数 \(a_{i}\) ,主席树空间开不下遂写了暴力。
点击查看代码
int a[1000010],b[1000010],vis[1000010],tot=0; vector<int>e[1000010],s[1000010]; void dfs(int x,int rt) { s[rt].push_back(x); vis[x]=tot; for(int i=0;i<e[x].size();i++) { if(vis[e[x][i]]!=tot) { dfs(e[x][i],rt); } } } bool check(int mid,int n,int m) { int last=0,minn; for(int i=1;i<=n;i++) { minn=0x7f7f7f7f; for(int j=0;j<s[a[i]].size()&&j<=mid;j++) { if(s[a[i]][j]>=last) { minn=min(minn,s[a[i]][j]); } } last=minn; if(last==0x7f7f7f7f) { return false; } } return true; } int main() { int testcase,n,m,l,r,mid,ans,i,j; scanf("%d",&testcase); for(j=1;j<=testcase;j++) { scanf("%d%d",&n,&m); for(i=1;i<=m;i++) { e[i].clear(); s[i].clear(); vis[i]=0; } for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=m;i++) { cin>>b[i]; e[i].push_back(b[i]); } for(i=1;i<=m;i++) { tot++; dfs(i,i); } l=0; r=m; ans=-1; while(l<=r) { mid=(l+r)/2; if(check(mid,n,m)==true) { ans=mid; r=mid-1; } else { l=mid+1; } } printf("%d\n",ans); } return 0; }
- \(16pts\)
-
正解
- 考虑双指针枚举最终的 \(a_{i}'\) ,判断 \(a_{i}\) 走至多 \(mid\) 步能否到达。
- 建反图方便断边后跑 \(DFS\) 。特殊分讨下有向基环树上两点间的距离即可。
点击查看代码
int a[1000010],b[1000010],dep[1000010],col[1000010],pos[1000010],rt[1000010],dfn[1000010],out[1000010],low[1000010],ins[1000010],cnt,tim,scc_cnt,tot,len; vector<int>e[1000010],scc[1000010]; stack<int>s; void tarjan(int x) { tim++; dfn[x]=low[x]=tim; ins[x]=1; s.push(x); for(int i=0;i<e[x].size();i++) { if(dfn[e[x][i]]==0) { tarjan(e[x][i]); low[x]=min(low[x],low[e[x][i]]); } else { if(ins[e[x][i]]==1) { low[x]=min(low[x],dfn[e[x][i]]); } } } if(dfn[x]==low[x]) { scc_cnt++; int k=0; while(x!=k) { k=s.top(); ins[k]=0; col[k]=scc_cnt; scc[scc_cnt].push_back(k); s.pop(); } } } void dfs(int x,int fa,int root) { tot++; dfn[x]=tot; rt[x]=root; dep[x]=dep[fa]+1; for(int i=0;i<e[x].size();i++) { if(col[e[x][i]]==0&&e[x][i]!=fa) { dfs(e[x][i],x,root); } } out[x]=tot; } int get_dis(int x,int y) { if(x==y) { return 0; } if(col[x]==0&&col[y]==0) { return (rt[x]==rt[y]&&dfn[y]<=dfn[x]&&dfn[x]<=out[y])?dep[x]-dep[y]:0x7f7f7f7f; } if(col[x]!=0&&col[y]!=0) { if(col[x]==col[y]) { return (pos[x]>pos[y])?pos[x]-pos[y]:pos[x]+(int)scc[col[x]].size()-pos[y]; } else { return 0x7f7f7f7f; } } if(col[x]==0) { int tmp=dep[x]; x=rt[x]; if(col[x]==col[y]) { if(pos[x]==pos[y]) { return tmp; } return (pos[x]>pos[y])?tmp+pos[x]-pos[y]:tmp+pos[x]+(int)scc[col[x]].size()-pos[y]; } else { return 0x7f7f7f7f; } } else { return 0x7f7f7f7f; } } bool check(int mid,int n,int m) { for(int i=1,j=1;i<=n;i++) { while(j<=m&&get_dis(a[i],j)>mid) { j++; } if(j>m) { return false; } } return true; } void bfs(int x,int co) { len++; pos[x]=len; for(int i=0;i<e[x].size();i++) { if(col[e[x][i]]==co&&pos[e[x][i]]==0) { bfs(e[x][i],co); } } } int main() { #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,m,l,r,mid,ans,i,j,k; scanf("%d",&t); dep[0]=-1; for(k=1;k<=t;k++) { scanf("%d%d",&n,&m); for(i=1;i<=scc_cnt;i++) { scc[i].clear(); } cnt=tim=scc_cnt=tot=0; while(s.empty()==0) { s.pop(); } for(i=1;i<=m;i++) { e[i].clear(); col[i]=dep[i]=rt[i]=dfn[i]=low[i]=ins[i]=pos[i]=0; } for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=m;i++) { cin>>b[i]; e[b[i]].push_back(i); } for(i=1;i<=m;i++) { if(dfn[i]==0) { tarjan(i); } } for(i=1;i<=m;i++) { if(scc[col[i]].size()==1&&b[i]!=i) { col[i]=0; } } for(i=1;i<=scc_cnt;i++) { if(scc[i].size()!=1) { for(j=0;j<scc[i].size();j++) { dfs(scc[i][j],0,scc[i][j]); } len=0; bfs(scc[i][0],i); } } for(i=1;i<=m;i++) { if(b[i]==i&&scc[col[i]].size()==1) { dfs(i,0,i); len=0; bfs(i,col[i]); } } l=0; r=m; ans=-1; while(l<=r) { mid=(l+r)/2; if(check(mid,n,m)==true) { ans=mid; r=mid-1; } else { l=mid+1; } } printf("%d\n",ans); } return 0; }
\(T6\) CF2023D Many Games \(9pts\)
-
部分分
- \(7pts\) :爆搜。
- \(9pts\) :背包。
点击查看代码
const double eps=1e-8; ll p[200010],w[200010]; double ans=0; unordered_map<ll,double>f; unordered_map<ll,double>::iterator itt; set<ll,greater<ll> >s; set<ll,greater<ll> >::iterator it; vector<ll>tmp; int main() { ll n,i; cin>>n; s.insert(0); f[0]=1; for(i=1;i<=n;i++) { cin>>p[i]>>w[i]; for(it=s.begin();it!=s.end();it++) { if(f.find(w[i]+*it)==f.end()) { f[w[i]+*it]=f[*it]*p[i]/100.0; } else { f[w[i]+*it]=max(f[w[i]+*it],f[*it]*p[i]/100.0); } tmp.push_back(w[i]+*it); } while(tmp.empty()==0) { s.insert(tmp.back()); tmp.pop_back(); } } for(itt=f.begin();itt!=f.end();itt++) { if((double)itt->first*itt->second-ans>eps) { ans=(double)itt->first*itt->second; } } printf("%.8lf\n",ans); return 0; }
-
正解
- 首先先特判掉 \(p_{i}=0/100\) 的情况。
- 设 \(x=\prod\limits_{i \in S} \frac{p_{i}}{100},y=\sum\limits_{i \in S}w_{i}\) 。对于 \((p,w)\) 能加入集合 \(S\) 当且仅当 \(xy<x \times \frac{p}{100} \times(y+w)\) ,即 \(y<\frac{pw}{100-p}\) ,得到 \(\sum\limits_{i \in S}w_{i}\) 的上界为 \(2 \times 10^{5}\) 。
- 对于同一个 \(p\) 一定优先选择 \(w\) 比较大的物品,不妨先对其降序排序。类似的,设同一个 \(p\) 已经选择了前 \(k\) 大的物品,能继续选择第 \(k+1\) 大的物品当且仅当 \((\frac{p}{100})^{k} \times \sum\limits_{i=1}^{k}w_{i}<(\frac{p}{100})^{k+1} \times \sum\limits_{i=1}^{k+1}w_{i}\) ,又因为 \(kw_{k+1} \le \sum\limits_{i=1}^{k}w_{i}\) 有 \(w_{i+1} \le \frac{\sum\limits_{i=1}^{k}w_{i}}{k}\),联立得到 \((\frac{p}{100})^{k} \times \sum\limits_{i=1}^{k}w_{i} \le (\frac{p}{100})^{k+1} \times \frac{k+1}{k} \times \sum\limits_{i=1}^{k}w_{i}\) ,即 \(k \le \frac{p}{100-p}\) ,于是只有前 \(1+\frac{p}{100-p} \le 100\) 个数才可能成为最优解。
点击查看代码
double f[200010]; vector<ll>v[110]; vector<pair<ll,ll> >e; int main() { // #define Issac #ifdef Issac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,p,w,sum=0,i,j,k; double ans=0; cin>>n; for(i=1;i<=n;i++) { cin>>p>>w; if(1<=p&&p<=99) { v[p].push_back(w); } sum+=(p==100)*w; } for(i=1;i<=99;i++) { sort(v[i].begin(),v[i].end(),greater<ll>()); k=i/(100-i); for(j=0;j<v[i].size()&&j<=k;j++) { e.push_back(make_pair(i,v[i][j])); } } f[0]=1; for(i=0;i<e.size();i++) { for(j=200000;j>=e[i].second;j--) { f[j]=max(f[j],f[j-e[i].second]*e[i].first/100.0); } } for(i=0;i<=200000;i++) { ans=max(ans,1.0*(sum+i)*f[i]); } printf("%.8lf\n",ans); return 0; }
总结
- \(T4\) 的部分分没来得及写。
- \(T5\)
- 以为最终连成的一个环,破环为链后分前后缀考虑,然后就口胡了个假的势能线段树维护在线二维数点。基本全场都在写 \(T5\) 。
- 需要特殊处理的部分想清楚再调,找基环树和求基环树上两点距离调了一个下午、一个晚上没调出来,但第二天想清楚后半小时就调完了。
后记
- 貌似 \(RMJ\) \(miaomiao\) 是拿他的公号交的,可能是学校 \(OJ\) 远端评测比赛的初试(?)。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18538477,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。