2024杭电第一场
1003 树
正解是开权值线段树暴力合并
那线段树合并的思想是什么?线段树合并就是线段树_合并啊!(bushi)
想象有两棵线段树,合并时就是对应的节点信息融合
比如现在区间[1,5],用num[rt]表示rt管辖的区间里数的个数
树a的num[rt]=3,树b的num[rt]=4,合并就是加起来=7
时间复杂度一般认为是nlog(n)的,1e5的数据能过
写法的话主要是merge函数和add函数,其他的pushup都是线段树常规操作
#include<bits/stdc++.h> using namespace std; const int N = 1e6+5; const int B = 20; #define mid ((l+r)>>1) #define ull unsigned long long int a[N],n,m; int root[N*B],tot=0; int num[N*B],ls[N*B],rs[N*B]; ull sum[N*B],sum2[N*B],val[N*B],ans[N*B]; vector<int>e[N]; void pushup(int t){ num[t]=num[ls[t]]+num[rs[t]]; sum[t]=sum[ls[t]]+sum[rs[t]]; sum2[t]=sum2[ls[t]]+sum2[rs[t]]; val[t]=num[ls[t]]*sum2[rs[t]]+val[ls[t]]+val[rs[t]]-sum[ls[t]]*sum[rs[t]]; } void add(int &t,int l,int r,int x){ if(!t) t=++tot; if(l==r){ num[t]++; sum[t]=(ull)x*num[t]; sum2[t]=(ull)x*x*num[t]; return; } if(x<=mid) add(ls[t],l,mid,x); else add(rs[t],mid+1,r,x); pushup(t); } int merge(int x,int y,int l,int r){ if(!x || !y) return x+y; if(l==r){ num[x]+=num[y]; sum[x]+=sum[y]; sum2[x]+=sum2[y]; return x; } ls[x]=merge(ls[x],ls[y],l,mid); rs[x]=merge(rs[x],rs[y],mid+1,r); pushup(x); return x; } void dfs(int u,int pre){ add(root[u],1,N,a[u]); for(auto v:e[u]){ if(v==pre) continue; dfs(v,u); merge(root[u],root[v],1,N); } ans[u]=val[root[u]]; } void solve(){ cin>>n; for(int i=1;i<=n-1;i++){ int u,v;cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } for(int i=1;i<=n;i++) cin>>a[i]; //cout<<"6"<<"\n"; dfs(1,0); ull res=0; for(int i=1;i<=n;i++) ans[i]*=2; for(int i=1;i<=n;i++) res^=ans[i]; cout<<res<<"\n"; } int main(){ ios_base::sync_with_stdio(false); cin.tie(0);cout.tie(0); int t=1; //cin>>t; while(t--){ solve(); } }
1012 并
考虑两维坐标都离散化后枚举每个格子,用二维前缀和计算其被覆盖的次数,设为cnt
则k固定时该格子的贡献为:( 1 - C[n-cnt][k] / C[n][k] ) * 该格子的面积
注意事项:1.处处取模 2.给的是点坐标,不是格点坐标,因此计算二维前缀和时要x++,y++
#include<bits/stdc++.h> using namespace std; const int N = 4e3+5; const int mod = 998244353; typedef long long ll; int n; int sum[N][N],tot[N]; ll f[N][N]; map<int,int>xid,yid; struct tringle{ int x,y,x2,y2; void deal(){ x=xid[x],x2=xid[x2]; y=yid[y],y2=yid[y2]; x++,y++; sum[x][y]++; sum[x][y2+1]--; sum[x2+1][y]--; sum[x2+1][y2+1]++; } }t[N]; int x[N*2],y[N*2]; int qpow(ll a,ll b){ ll ret=1; while(b){ if(b&1) ret=ret*a%mod; a=a*a%mod; b>>=1; } return ret; } int inv(int x){ return qpow(x,mod-2); } int main(){ ios_base::sync_with_stdio(false); cin.tie(0);cout.tie(0); f[0][0]=1; for(int i=1;i<N;i++){ f[i][0]=1; for(int j=1;j<=i;j++) f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod; } cin>>n; int cntx=0,cnty=0; for(int i=1;i<=n;i++){ cin>>t[i].x>>t[i].y>>t[i].x2>>t[i].y2; x[++cntx]=t[i].x;x[++cntx]=t[i].x2; y[++cnty]=t[i].y;y[++cnty]=t[i].y2; } sort(x+1,x+cntx+1); sort(y+1,y+cnty+1); cntx=unique(x+1,x+cntx+1)-(x+1); cnty=unique(y+1,y+cnty+1)-(y+1); for(int i=1;i<=cntx;i++){ xid[x[i]]=lower_bound(x+1,x+cntx+1,x[i])-(x); // cout<<x[i]<<" "<<xid[x[i]]<<"\n"; } for(int i=1;i<=cnty;i++){ yid[y[i]]=lower_bound(y+1,y+cnty+1,y[i])-(y); // cout<<y[i]<<" "<<yid[y[i]]<<"\n"; } for(int i=1;i<=n;i++) t[i].deal(); for(int i=1;i<=cntx;i++){ for(int j=1;j<=cnty;j++){ int tmp=(sum[i-1][j]+sum[i][j-1])%mod; tmp=(tmp-sum[i-1][j-1]+mod)%mod; sum[i][j]+=tmp; sum[i][j]%=mod; tot[sum[i][j]]+=1ll*(x[i]-x[i-1])*(y[j]-y[j-1])%mod; tot[sum[i][j]]%=mod; } } ll ans[n+5]={0}; for(int k=1;k<=n;k++){ int inv_c_n_k=inv(f[n][k]); for(int cnt=1;cnt<=n;cnt++){ ll tmp=(1-1ll*f[n-cnt][k]*inv_c_n_k%mod+mod)%mod; tmp=tmp*tot[cnt]%mod; ans[k]=(ans[k]+tmp)%mod; } } for(int i=1;i<=n;i++) cout<<ans[i]<<"\n"; }
1005 博弈
分奇偶考虑。
偶数:
有一个奇数字符就是五五开,一定不会平局(想象一下两个人拿到的序列互换,这是对称的)
如果没有的话就是可能会平局,剩下的再五五开,答案为(1-平局的概率)/2
奇数:
a肯定会比b多拿1个
假设只有1个奇数,且前面相等、奇数在最后拿就是a赢,剩下的情况还是五五开。算出a赢的概率设为p,加上(1-p)/2,答案就是 p + (1-p)/2 得到 (1+p)/2 。
假设有2个奇数,那就是五五开。
平局的概率:
把拿出来的序列当成一个排列,前面为a拿的,后面半段为b拿的。假设每个字母都不一样,则这个排列的总方案数为sum!。
考虑固定前面的一半,对于每个字母都是抽cnt/2个放在前面,于是乘上C(cnt,cnt/2),再进行排列就是(sum/2)! 。
当前面的排列固定后后面半段也随之固定了,不过后面的字母间还有顺序,对于每个字母再乘上(cnt/2)!。
排列组合要恶补一下了👉👈
#include<bits/stdc++.h> using namespace std; #define int long long typedef long long ll; const int N = 1e7+5; const int mod = 998244353; ll fact[N],inv[N]; ll power(ll a, ll b, ll p){ if(b==0) return 1; if(a==0) return 0; ll res=1; a%=p,b%=p; while(b>0){ if(b&1) res=(1ll*res*a)%p; b>>=1; a=(1ll*a*a)%p; } return res; } ll _inv(ll a){ return power(a,mod-2,mod); } void pre(){ fact[0]=1;inv[0]=1; for(int i=1;i<N;i++) fact[i]=(i*fact[i-1])%mod; inv[N-1]=_inv(fact[N-1]); for(int i=N-2;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod; } int C(int n, int r, int p) { if(r>n || r<0) return 0; if(n==r) return 1; if (r==0) return 1; return (((fact[n]*inv[r]) % p )*inv[n-r])%p; } void solve(){ int n;cin>>n; map<char,int>mp; int flag=0; int sum=0; for(int i=1;i<=n;i++){ char c;int h; cin>>c>>h; mp[c]=h; flag+=(h&1); sum+=h; } if(sum%2==0){ if(flag) { cout<<_inv(2)<<"\n"; return ; } else{ ll tot=_inv(fact[sum]); ll a=1; for(char i='a';i<='z';i++){ if(!mp[i]) continue; int cnt=mp[i]; a=a*C(cnt,cnt/2,mod)%mod; } for(char i='a';i<='z';i++){ if(!mp[i]) continue; int cnt=mp[i]; a=a*fact[cnt/2]%mod; } a=a*fact[sum/2]%mod; ll p=a*tot%mod; ll out=(1-p+mod)%mod*_inv(2)%mod; cout<<out<<"\n"; } } else { if(flag==1){ ll a=1; sum-=1; ll tot=_inv(fact[sum+1]); for(char i='a';i<='z';i++){ if(!mp[i]) continue; int cnt=mp[i]; if(cnt&1) { a=cnt; mp[i]--; } } for(char i='a';i<='z';i++){ if(!mp[i]) continue; int cnt=mp[i]; a=a*C(cnt,cnt/2,mod)%mod; } for(char i='a';i<='z';i++){ if(!mp[i]) continue; int cnt=mp[i]; a=a*fact[cnt/2]%mod; } a=a*fact[sum/2]%mod; ll p=a*tot%mod; ll out=(1+p)%mod*_inv(2)%mod; cout<<out<<"\n"; } else { cout<<_inv(2)<<"\n"; } } } signed main(){ ios_base::sync_with_stdio(false); cin.tie(0);cout.tie(0); pre(); int t;cin>>t; while(t--){ //TODO solve(); } }
1001
处理出每个循环串的哈希值丢到一个map里,在大串中查询[ i-len,i ]的哈希值是否在map中出现过,是则+1
不要被骗去写后缀自动机了,这题空间给的很小
#include<bits/stdc++.h> using namespace std; const int N = 1048576*2; const int base=13331; typedef unsigned long long ull; ull z[N],p[N],f[N]; ull get1(int l,int r){ return z[r]-z[l-1]*p[r-l+1]; } ull get2(int l,int r){ return f[r]-f[l-1]*p[r-l+1]; } void solve(){ map<ull,int>mp; string s,t;cin>>s>>t; s=s+s; s=" "+s; p[0]=1; int n=s.length(); n--; for(int i=1; i<=n; i++) { z[i]=z[i-1]*base-s[i]-'a'+1; p[i]=p[i-1]*base; } t=" "+t; int m=t.length(); m--; for(int i=1; i<=m; i++) { f[i]=f[i-1]*base-t[i]-'a'+1; } int ans=0; for(int i=1;i<=n/2;i++){ mp[get1(i,i+n/2-1)]=1; } for(int j=n/2;j<=m;j++){ if(mp[get2(j-n/2+1,j)]) ans++; } cout<<ans<<"\n"; } int main(){ ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0); int t;cin>>t; while(t--){ //TODO solve(); } }
1002
暴力dp就可以
正解是 nsqrt(k) 的做法,考虑把读入的数据随机打乱,则在第i格预计的期望是 k/n*i,在这个范围附近找即可,但步长没看懂为什么是v*sqrt(k)
#include<bits/stdc++.h> using namespace std; const int N = 1005; typedef long long ll; ll dp[N*4],a[N],b[N],c[N],d[N]; void solve(){ int n,k;cin>>n>>k; for(int i=1;i<=n;i++) { cin>>a[i]>>b[i]>>c[i]>>d[i]; } dp[0]=0; for(int j=1;j<=k;j++) dp[j]=1e16; // cout<<666<<"\n"; for(int i=1;i<=n;i++){ for(int j=k;j>=1;j--){ if(j>=1) dp[j]=min(dp[j],dp[j-1]+a[i]); if(j>=2) dp[j]=min(dp[j],dp[j-2]+b[i]); if(j>=3) dp[j]=min(dp[j],dp[j-3]+c[i]); if(j>=4) dp[j]=min(dp[j],dp[j-4]+d[i]); // cout<<i<<" "<<j<<" "<<dp[j]<<"\n"; } } cout<<dp[k]<<"\n"; } int main(){ ios_base::sync_with_stdio(false); cin.tie(0);cout.tie(0); int t;cin>>t; while(t--){ //TODO solve(); } }
1008
队友做的
#include<bits/stdc++.h> #define int long long using namespace std; const int K=15,N=1<<K; int n,k,ans; inline int read() { int x=0;bool f=1;char ch=getchar(); for(;ch<'0'||ch>'9';ch=getchar())f^=(ch=='-'); for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch^48); return f?x:-x; } void Kafka() { n=read(),k=read(); ans=1; for(int i=0;i<k;++i) { int res=0; if(n&(1<<i)) res=12; else res=4; ans*=res; } cout<<ans<<endl; } signed main() { for(int T=read();T--;)Kafka(); return 0; }