noip模拟17
A 选取字符串
会 。
直接枚举全部的 然后开一个二维的 bitset 去存一个数是否是某个数的前后缀。
选到两个 时把这两个数的 bitset 与起来,贡献是 。
正解就是先用 kmp 去求出来全部的 border,然后用 border 关系建一棵树,这棵树上满足父亲是儿子的 border。根节点为空串。
然后就是树上选取 个点,求 lca 深度平方和。
点击查看代码
#include<bits/stdc++.h> using namespace std; #define int long long const int N=1e6+10,mod=998244353; int f[N],g[N]; int ppow(int a,int b) { int res=1; while(b) { if(b&1) res=(res*a)%mod; a=(a*a)%mod,b>>=1; }return res; } int n,k; void init() { f[0]=g[0]=1; for(int i=1;i<=n+2;i++) f[i]=(f[i-1]*i)%mod; g[n+1]=ppow(f[n+1],mod-2); for(int i=n;i>=1;i--) g[i]=(g[i+1]*(i+1))%mod; } char c[N]; int nxt[N],dep[N],sum[N],cnt[N]; inline int getc(int n,int m) { if(n<m) return 0; return f[n]*g[m]%mod*g[n-m]%mod; } signed main() { // freopen("a2.in","r",stdin); freopen("string.in","r",stdin); freopen("string.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>k>>(c+1); n=strlen(c+1); init(); // cout<<n<<"\n"; for(int i=2,j=0;i<=n;i++) { for(;j>0&&c[i]!=c[j+1];j=nxt[j]); if(c[i]==c[j+1]) j++;nxt[i]=j; } for(int i=1;i<=n;i++) dep[i]=dep[nxt[i]]+1; for(int i=0;i<=n;i++) sum[i]++; for(int i=n;i>=1;i--) sum[nxt[i]]=sum[nxt[i]]+sum[i]; for(int i=0;i<=n;i++) cnt[dep[i]]=(cnt[dep[i]]+getc(sum[i],k))%mod; int ans=0; for(int i=0;i<=n;i++) ans=(ans+1ll*cnt[i]*(2*i+1)%mod)%mod; printf("%d",ans); return 0; }
B 取石子
其实没看懂题解。然后就粘过来了。。。
问题模型:取石子(NIM)游戏,要求每个人每次取的石子数不能超过上一个人刚刚取的,第一个人最
开始可以取不超过 个。
考虑策略:
-
如果 是奇数,先手取 个必胜,因为每个人之后都只能取 个;
-
否则,先手最优一定取偶数个并且后面每个人能取偶数个都只会取偶数个(否则留给对手总和为奇
数的情况,自己必败),所以可以递归到 。
解得先手必胜当且仅当对于某个 , 模 和
为 ,也即 。
定义 表示整除 的最大的 的幂。先手第一步能必胜的策略必定是取 个。枚举取的堆,假设是第 堆,考虑枚举取完之后另一个人面对的剩下的异或和的 ,假设是 ,那么先手取的个数为 。由于必胜,后手不能取到 ,于是自己这次取的个数也必须小于 。枚举 依次判断即可。注意处理 (取完之后异或和归零)的情况。
我的理解比较浅层,但是算一点:
-
对于和为偶数的情况,所有选取的答案一定为偶数,奇数同理。
-
对于奇数情况,必胜顺序一定是第一次先手选一个奇数,然后后手开始选偶数,即上述两种情况可以化为一起。
-
对于偶数的选取情况,若 为偶数,则选取的数一定是 的倍数,否则模 余 。
但是我至今没有理解为什么取偶数个都会取偶数个。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n,k; inline int lowbit(int x) { if(x==0) return 1e9; return x&-x; } const int N=5e4+6; int *a; signed main() { freopen("nim.in","r",stdin); freopen("nim.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); int sum=0; cin>>n>>k; a=new int [n+2]; for(int i=1;i<=n;i++) { cin>>a[i],sum^=a[i]; } if(lowbit(sum)>k) return cout<<0,0; cout<<"1\n"; for(int i=1;i<=n;i++) { int now=0,r=sum^a[i]; while(114) { bool flag=0; for(int j=0;j<=30;j++) { if(now+(1<<j)>min(a[i],k)) continue; int kk=now+(1<<j); if(lowbit((a[i]-kk)^r)>kk) { now=kk,flag=1;break; } } if(!flag) break; cout<<i<<" "<<now<<"\n"; } } }
C 均衡区间
首先,处理所有数左边第一个大于它的和小于它的数是显然的。贡献会加在这两个点 min 的外面。
然后就有一个跑的极快的假做法,虽然会被单调序列卡到 。
如下。每次跳最大和最小的位置,然后更新答案。用补集的形式搞,所以不用处理合法的答案。
点击查看代码
#include<bits/stdc++.h> using namespace std; const int N=1e6+6; inline int read() { register int s=0; register char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') s=(s<<1)+(s<<3)+(c^48),c=getchar(); return s; } void write(int x) { if(x>9) write(x/10); putchar(x%10+'0'); } int n,id,a[N],ans[2][N],mx[N],mi[N],st1[N],st2[N],top1,top2,b[N]; signed main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); n=read(),id=read(); if(id==2) { for(int i=1;i<=n;i++) putchar('0'),putchar(' '); putchar(10); for(int i=1;i<=n;i++) putchar('0'),putchar(' '); putchar(10);return 0; } for(int i=1;i<=n;i++) a[i]=read(),ans[0][i]=n-i+1,ans[1][i]=i; for(int i=n;i;i--) { while(top1&&a[st1[top1]]<a[i]) mx[st1[top1--]]=i; while(top2&&a[st2[top2]]>a[i]) mi[st2[top2--]]=i; st1[++top1]=i,st2[++top2]=i; } for(int i=1;i<=n;i++) { int j=min(mx[i],mi[i]); if(!j)j++; ans[1][i]-=i-j+1; --b[j],++b[i+1]; int k=mx[i]; while(k>=j) k=mx[k]; while(k) ans[1][i]--,ans[0][k]--,k=mx[k]; k=mi[i]; while(k>=j) k=mi[k]; while(k) ans[1][i]--,ans[0][k]--,k=mi[k]; } for(int i=1;i<=n;i++) { b[i]+=b[i-1]; write(ans[0][i]+b[i]),putchar(' '); } putchar(10); for(int i=1;i<=n;i++) { write(ans[1][i]),putchar(' '); } }
正解其实就是把上面的处理看作二维数点问题,用树状数组解决。
代码来自 why
#include<bits/stdc++.h> #define N 1000005 #define pb push_back using namespace std; int n,id; int a[N]; int L[N],R[N],stk[N],tot; vector<int> s[N],t[N]; struct BIT { int c[N]; void add(int x,int y) {while(x <= n){c[x] += y; x += (x & (-x));}} int sum(int x) { int res = 0; while(x) {res += c[x];x -= (x & (-x));} return res; } void cl() {memset(c,0,sizeof(c));} }B; int main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); scanf("%d%d",&n,&id); for(int i = 1;i <= n;i++) scanf("%d",&a[i]); for(int i = 1;i <= n;i++) { while(tot && a[i] <= a[stk[tot]]) tot--; L[i] = stk[tot] + 1; stk[++tot] = i; } tot = 0; for(int i = 1;i <= n;i++) { while(tot && a[i] >= a[stk[tot]]) tot--; L[i] = min(L[i],stk[tot] + 1); stk[++tot] = i; } tot = 0;stk[tot] = n + 1; for(int i = n;i >= 1;i--) { while(tot && a[i] >= a[stk[tot]]) tot--; R[i] = stk[tot] - 1; stk[++tot] = i; } tot = 0;stk[tot] = n + 1; for(int i = n;i >= 1;i--) { while(tot && a[i] <= a[stk[tot]]) tot--; R[i] = max(R[i],stk[tot] - 1); stk[++tot] = i; } // for(int i = 1;i <= n;i++) printf("%d %d\n",L[i],R[i]); int res = 0; for(int i = 1;i <= n;i++) s[L[i]].pb(i),t[i].pb(i); for(int i = 1;i <= n;i++) { for(int j : s[i]) B.add(j,1),res++; int buok = res - (B.sum(R[i]) - B.sum(i - 1));//总数减去合法部分 int ans = (n - R[i]) - buok; printf("%d ",ans); for(int j : t[i]) B.add(j,-1),res--; } printf("\n"); res = 0; B.cl(); for(int i = 1;i <= n;i++) s[i].clear(),t[i].clear(); for(int i = 1;i <= n;i++) s[i].pb(i),t[R[i]].pb(i); for(int i = 1;i <= n;i++) { for(int j : s[i]) B.add(j,1),res++; int buok = res - (B.sum(i) - B.sum(L[i] - 1)); int ans = (L[i] - 1) - buok; printf("%d ",ans); for(int j : t[i]) B.add(j,-1),res--; } return 0; }
D 禁止套娃
分的 dp 是可做的。设 表示末尾小于等于 的不同子序列个数,那么 。
注意 的时候 。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析