uoj的题目都挺好的。
A.新年的XOR
观察性质,$O(1)$的。
#include <bits/stdc++.h> #define for1(a,b,i) for(int i=a;i<=b;++i) #define FOR2(a,b,i) for(int i=a;i>=b;--i) using namespace std; typedef long long ll; inline int read() { int f=1,sum=0; char x=getchar(); for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1; for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0'; return f*sum; } int main () { int Test_=read(); while (Test_--) { ll n; cin>>n; if(n==0) puts("8 67"); else if(n==1) puts("2 3"); else if(n==2) puts("3 9"); else if(n==3) puts("3 7"); else if(n==4) puts("97 100"); else if(n%4==0) printf("%lld %lld ",n-4,n); else if(n%4==1) printf("2 %lld ",n-1); else if(n%4==2) printf("2 %lld ",n); else printf("%lld %lld ",n-3,n-1); } }
B.新年的叶子
需要分析树的直径的性质。
我们需要证明树的每一条直径都会相交与某一条直径的中点。
反证法证明长度会变长即可。
设我们找到的点为$root$。
显然我们可以将叶子分成$root$不同儿子的块。
若直径的长度为偶数,则只有$dep$(点到$root$的距离)为$frac{len}{2}$的点有用。
否则,$dep$为可以为$frac{len}{2}$或$frac{len}{2}-1$。
对于第一种情况,我们可以分出$a_{i}$代表第$i$个儿子的有用节点个数,还剩下一些没用的节点。
对于第二种情况,显然长度为$frac{len}{2}$的点属于一个儿子,那么我们可以合并所有长度为$frac{len}{2}-1$的点。
这样就将模型转化成了给定$n$个$a_{i}$和$m$个没用的点,每次选出一个点标记为黑色。
可以重复选,知道只剩下一个集合有未标记的点的期望步数。
我们枚举最后剩下那个集合,计算贡献。
设这个集合的$a_{i}$为$t$,$sum a_{i}=sum$,$m$个无用点,则:
$sum_{i=1}^{t}sum_{j=0}^{m}(sum-i+j)!*frac{sum-t}{sum-i+j}inom{t}{i}*inom{m}{j}*f[sum-i+j]*frac{(m-j+i)!}{(sum+m)!}$
$i,j$分别是这个集合剩下点的个数以及插入的无用的点的个数。
$f[i]$代表从0走到$i$个点的期望步数,显然为$sum_{i=1}^{x}frac{n}{n-i+1}$。
这样过不了,然后我们发现$j-i$是一个定值我们只需要化简如下式子:
$sum_{i=0}^{n}inom{n}{i}*inom{m}{c+i}=sum_{i=0}^{n}inom{n}{n-i}*inom{m}{c+i}=inom{n+m}{n+c}$
然后就变成线性的做法了!
UPD:我傻了,上述我的推导是$O(n*sqrt(n))$的算法,只不过出题人没有卡,我自己hack了自己。。。
我真的好傻啊,其实无用点是不用管的,因为他不管选没选都可以再选,这样就可以理解为选第useless+i个点。
然后就把那个难处理的无用点个数就抛弃了!我好傻啊。注释掉的就是原来的代码。
#include <bits/stdc++.h> #define for1(a,b,i) for(int i=a,end_=b;i<=end_;++i) #define FOR2(a,b,i) for(int i=a,end_=b;i>=end_;--i) using namespace std; typedef long long ll; #define M 1000005 #define mod 998244353 int n,cnt; int f[M],js[M],ni[M],inv[M]; int pos,root,e_size,head[M],du[M],dep[M],Max[M]; vector <int> sta; struct node {int v,nxt;}e[M*2]; inline void e_add(int u,int v) { e[++e_size]=(node){v,head[u]}; head[u]=e_size; } inline int qpow(int x,int ci) { int sum=1; for(;ci;ci>>=1,x=1ll*x*x%mod) if(ci&1) sum=1ll*x*sum%mod; return sum; } inline int get_C(int x,int y) { if(x<y) return 0; return 1ll*js[x]*ni[y]%mod*ni[x-y]%mod; } inline void inc(int &x,int y) {x+=y,x-=x>=mod?mod:0;} inline void dfs(int x,int fa,int now) { dep[x]=Max[x]=dep[fa]+1; cnt+=now==dep[x]&&du[x]==1; for(int i=head[x];i;i=e[i].nxt) { int v=e[i].v; if(v==fa) continue; dfs(v,x,now); Max[x]=max(Max[x],Max[v]); } } int main () { //freopen("a.in","r",stdin); scanf("%d",&n); for1(2,n,i) { int x,y; scanf("%d%d",&x,&y); ++du[x],++du[y]; e_add(x,y),e_add(y,x); } dfs(1,0,0); for1(1,n,i) if(dep[i]>dep[pos]) pos=i; dfs(pos,0,0); for1(1,n,i) if(Max[i]>Max[root]&&(Max[i]+1>>1)==dep[i]) root=i; if(Max[pos]&1) { dfs(root,0,0); pos=0; for1(1,n,i) if(dep[i]>dep[pos]) pos=i; for(int i=head[root];i;i=e[i].nxt) { int v=e[i].v; cnt=0; dfs(v,root,dep[pos]); if(cnt) sta.push_back(cnt); } } else { dfs(root,0,0); pos=0; for1(1,n,i) if(dep[i]>dep[pos]) pos=i; cnt=0; dfs(root,0,dep[pos]); sta.push_back(cnt); cnt=0; dfs(root,0,dep[pos]-1); sta.push_back(cnt); } js[0]=1; for1(1,n,i) js[i]=1ll*i*js[i-1]%mod; ni[n]=qpow(js[n],mod-2); FOR2(n,1,i) ni[i-1]=1ll*i*ni[i]%mod; for1(1,n,i) inv[i]=1ll*ni[i]*js[i-1]%mod; /* int ans=0; int sum=0,tot_x=0; for1(1,n,i) tot_x+=du[i]==1; for1(1,sta.size(),i) sum+=sta[i-1]; for1(1,tot_x,i) inc(f[i]=f[i-1],1ll*tot_x*inv[tot_x-i+1]%mod); tot_x-=sum; for1(1,sta.size(),i) { int sel=0; int t=sta[i-1]; for1(-t,tot_x-1,j) { sel=get_C(t+tot_x,t+j); if(j>=0) inc(sel,mod-get_C(tot_x,j)); inc(ans,1ll*(sum-t)*js[sum+j]%mod*inv[sum+j]%mod*f[sum+j]%mod*js[tot_x-j]%mod*sel%mod); } } ans=1ll*ans*ni[sum+tot_x]%mod; cout<<ans<<endl; */ int ans=0; int sum=0,tot_x=0; for1(1,n,i) tot_x+=du[i]==1; for1(1,sta.size(),i) sum+=sta[i-1]; for1(1,sum,i) inc(f[i]=f[i-1],1ll*tot_x*inv[sum-i+1]%mod); for1(1,sta.size(),i) { int t=sta[i-1]; for1(1,t,j) inc(ans,1ll*js[sum-j-1]*(sum-t)%mod*f[sum-j]%mod*get_C(t,j)%mod*js[j]%mod*ni[sum]%mod); } cout<<ans<<endl; }
C.新年的叶子
一眼上去simpson积分,然后只拿了50分。
random_shuffle了一下之后拿了80分。
感觉还有很多优化的余地,比如说最后一个点就不用积分了,直接解出区间即可。
正解非常巧妙,将一个小数分成了整数部分和小数部分。
对于$li=ri$我们直接令他为一个确定的解,否则在$[li,ri-1]$中枚举整数部分。
另一个数为$p_{i}+q_{i}$,$p_{i}$为整数部分。则限制条件如下:
$q_{i}-q_{j}geq a_{i,j}-p_{i}+p_{j}$
设后面的整数部分为$c$。
若$c>0$显然无解。
若$c<0$显然没有限制。
若$c=0$才会有$q_{i}>q_{j}$的限制。
这样就好做了,直接dp出满足条件的排列个数即可。
#include <bits/stdc++.h> #define for1(a,b,i) for(int i=a,end_=b;i<=end_;++i) #define FOR2(a,b,i) for(int i=a,end_=b;i>=end_;--i) using namespace std; typedef long long ll; inline int read() { int f=1,sum=0; char x=getchar(); for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1; for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0'; return f*sum; } #define M 10 #define N 40 int n,p[M]; double ans; int f[N],l[M],r[M],a[M][M]; inline double calc_() { int to[M]={0}; for1(1,n,i) for1(1,n,j) if(i!=j) { int x=a[i][j]-p[i]+p[j]; bool t[2]={l[i]==r[i],l[j]==r[j]}; if(x<0) continue; if(x>0) return 0; if(t[0]&&!t[1]) return 0; if(!t[0]&&!t[1]) to[i]|=1<<j-1; } memset(f,0,sizeof(f)); f[0]=1; for1(1,(1<<n)-1,i) { bool vis=0; for1(1,n,j) if((i>>j-1&1)&&(l[j]==r[j])) vis=1; if(vis) continue; for1(1,n,j) if(i>>j-1&1) { int t=i^(1<<j-1); if((t&to[j])!=to[j]) continue; f[i]+=f[t]; } } int cnt=0,all=(1<<n)-1; for1(1,n,i) if(l[i]==r[i]) ++cnt,all^=1<<i-1; double sum=f[all]; for1(1,n-cnt,i) sum/=i; return sum; } inline void dfs(int x) { if(x==n+1) return ans+=calc_(),void(); if(l[x]==r[x]) { p[x]=l[x]; dfs(x+1); } else { for1(l[x],r[x]-1,i) { p[x]=i; dfs(x+1); } } } int main() { n=read(); for1(1,n,i) l[i]=read(),r[i]=read(); for1(1,n,i) for1(1,n,j) a[i][j]=read(); for1(1,n,i) if(a[i][i]>0) return puts("0.00000000"),0; dfs(1); for1(1,n,i) if(l[i]!=r[i]) ans/=r[i]-l[i]; printf("%.8f ",ans); }
D.新年的代码
想到了AGC027E的思路。
但是无法证明为什么分段一定是最优的以及没有发现一段的贡献为$n-1$或$n$。
首先我们考虑变换在模3意义下是不变的。
性质1:对于一个串S,我们可以花费最多n步使得前n-1个字符变成目标字符串。
数学归纳法,花费n步都是因为最后剩下两个字符花费了两步,例如$ab$到$ba$。
这时我们可以省下这两步,让最后三个花费3的代价,总步数为$n-1-2+3=n$。
性质2:对于串$s1s2,t1t2$。若$s1=t1,s2=t2$,则合并不优。
这里$=$定义为长度相等且$mod$3之后相等。
因为合并我们至少要在交界处操作一次,次数:$1+n1+n2-1=n1+n2$。
然后我们就可以check一段是否可以用n-1次操作来弄,否则为n次。
因为操作为n-1次,所以$forall i,i+1$都要被操作一次。
这个非常好证,如果不是这样则说明可以分更小的段,与前提不符。
这样影响的就只是操作顺序了。
然后就可以直接dp了,$f[i][a][b]$代表$1-i-1$都相等,第$i$个字符为$a,b$是否可以。
#include <bits/stdc++.h> #define for1(a,b,i) for(int i=a,end_=b;i<=end_;++i) #define FOR2(a,b,i) for(int i=a,end_=b;i>=end_;--i) using namespace std; typedef long long ll; inline int read() { int f=1,sum=0; char x=getchar(); for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1; for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0'; return f*sum; } #define M 500005 int n; char s[M],t[M]; bool f[M][3][3]; inline int st(char x) { if(x=='G') return 1; return x=='B'?0:2; } inline bool check(int l,int r) { memset(f[l],0,sizeof(f[l])); f[l][s[l]][t[l]]=1; for1(l,r-1,i) { memset(f[i+1],0,sizeof(f[i])); for1(0,2,j) for1(0,2,k) { if(!f[i][j][k]) continue; if(s[i+1]!=k) f[i+1][(j+s[i+1]-k+3)%3][t[i+1]]=1; if(t[i+1]!=j) f[i+1][s[i+1]][(k+t[i+1]-j+3)%3]=1; } } for1(0,2,i) if(f[r][i][i]) return 1; return 0; } int main() { //freopen("a.in","r",stdin); n=read(); scanf("%s%s",s+1,t+1); for1(1,n,i) s[i]=st(s[i]),t[i]=st(t[i]); int ans=0; for(int l=1,r;l<=n;l=r+1) { r=l; int c[2]={s[l],t[l]}; while (c[0]!=c[1]) { ++r; c[0]=(c[0]+s[r])%3; c[1]=(c[1]+t[r])%3; } if(l==r) continue; ans+=r-l+1-check(l,r); } printf("%d ",ans); }