NOIP提高组模拟赛14
A. 异或
这种题就是打表,什么性质不性质的不重要
不知道我打表出的式子怎么解释,,
粘一下题解做法吧
"考虑每一位的贡献,从低到高的第\(i\)位会每隔\(2^i\)个数变化一次,于是第$$i位对答案的贡献就是\(\lfloor\frac{i}{2^i}\rfloor\),
把每一位的贡献加起来即可。"
我的代码
code
#include <cstdio>
using namespace std;
unsigned long long n;
int main(){
scanf("%llu",&n);
unsigned long long base=2;
unsigned long long ans=0;
while(n){
if(n&1)ans+=base-1;
base<<=1;
n>>=1;
}
printf("%llu\n",ans+n);
return 0;
}
B. 赌神
考场理解错了题意,导致送的分都没拿到
考虑\(n=2\)的情况
设\(dp[i][j]\)表示第一种颜色的球剩\(i\)个,第二种颜色的球剩\(j\)个对手中筹码的最大贡献,也可以说是表示你当前有1个筹码,箱子里两种颜色的球分别有\(i,j\)个,你最终能获得多少筹码,这个“1”是单位“1”
由于幕后黑手肯定会按你得筹码最少的情况掉球,所以在最优策略下,不同的掉球情况下最终所能获得的筹码数应该相同
假设你在第一种颜色上押\(x\)个筹码,在第二种颜色上押\(y\)个筹码,有\(x\times dp[i-1][j]=y\times dp[i][j-1]\)又有\(x+y=1\)可以解得\(x\)
容易发现全押一定比不押优(\(dp[i][j]>=1\)),于是有\(dp[i][j]=\frac{2dp[i-1][j]dp[i][j-1]}{dp[i-1][j]+dp[i][j-1]}\)
发现那个系数\(2\)看着不好看,而且可以最后再乘上
所以设\(f[i][j]\times 2^{i+j}=dp[i][j]\)
那么有\(\frac{1}{f[i][j]}=\frac{1}{f[i-1][j]}+\frac{1}{f[i][j-1]}\)
好眼熟,这不是二维图中只能向右上走的方案数吗,直接\((^{i+j}_{\;\;i})\)
答案就是\(\frac{2^{i+j}}{(^{i+j}_{\;\;i})}\)
结论可以扩展到多维情况
code
#include <cstring>
#include <cstdio>
using namespace std;
const int mod=998244353;
const int maxn=1000005;
int n,x[maxn+15];
long long jc[maxn+15],inv[maxn+15];
long long qpow(long long x,long long y){
long long ans=1;
while(y){
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
void ycl(){
jc[0]=1;jc[1]=1;inv[0]=1;
for(int i=2;i<=maxn;++i)jc[i]=jc[i-1]*i%mod;
inv[maxn]=qpow(jc[maxn],mod-2);
for(int i=maxn-1;i>=1;--i)inv[i]=inv[i+1]*(i+1)%mod;
}
long long get_C(int n,int m){return jc[n]*inv[m]%mod*inv[n-m]%mod;}
int main(){
scanf("%d",&n);ycl();
for(int i=1;i<=n;++i)scanf("%d",&x[i]);
long long sum=0;
for(int i=1;i<=n;++i)sum+=x[i];
long long ans = qpow(n,sum);
long long in=1;
for(int i=1;i<=n;++i){
in=in*get_C(sum,x[i])%mod;
sum-=x[i];
}
printf("%lld\n",ans*qpow(in,mod-2)%mod);
return 0;
}
C. 路径
题解不做人,给了个错误式子。。
正解确实要斯特林数,但是还需要换根\(DP\)
有\(x^k=\sum_{i=1}^k \begin{Bmatrix}k\\ i\end{Bmatrix}\times x^{\underline i}\)
\((x+1)^{\underline i}=i\times x^{\underline{i-1}}+x^{\underline i}\)
设\(f[i][j]\)表示\(i\)子树内到\(i\)距离的\(j\)次下降幂之和,\(g[i][j]\)表示\(i\)子树内到\(i\)的父亲距离的\(j\)次下降幂之和
\(f_{i,j}=\sum_{u\in son_i}g_{u,j}\)
\(g_{i,j}=j\times f_{i,j-1}+f_{i,j}\)
看码注释吧。。
code
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=1000005;
const int maxk=105;
const int mod=998244353;
const int inv_2=499122177;//2在%mod意义下的逆元
struct edge{
int net,to;
}e[maxn<<1|1];
int head[maxn],tot,n,k;
void add(int u,int v){
e[++tot].net=head[u];
head[u]=tot;
e[tot].to=v;
}
int s[maxk][maxk];//斯特林数
int f[maxn][maxk];//f[x][i]x子树内所有点到x的距离的i次下降幂之和
int g[maxn][maxk];//g[x][i]x子树内所有点到x的父亲距离的i次下降幂之和
void pre(){
s[1][1]=1;
for(int i=2;i<=k;++i)
for(int j=1;j<=i;++j)
s[i][j]=(s[i-1][j-1]+1ll*s[i-1][j]*j%mod)%mod;
//第二类斯特林数递推,s(n,m)将n个元素划分为m个非空集合的方案数
//s(n,m)=s(n-1,m-1)+s(n-1,m)*m,前n-1个元素划分为m-1个集合,当前元素只能在第m个集合,前n-1个元素分为m个集合,当前元素可以在任意集合
}
void dfs(int x,int fa){
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==fa)continue;
dfs(v,x);
for(int i=0;i<=k;++i){
if(!g[v][i])break;
f[x][i]=(f[x][i]+g[v][i])%mod;
}
}
++f[x][0];
for(int i=k;i;--i)
g[x][i]=(1ll*i*f[x][i-1]%mod+f[x][i])%mod;//x+1的i次下降幂==(x的i-1次下降幂乘以i )+x的i次下降幂
g[x][0]=f[x][0];
}
int tmp[maxk],sum[maxk];
//换根DP,统计所有点到当前根的贡献,每条路径会被计算两次,最后需要乘2的逆元
void dfs_get(int x,int fa){
for(int i=0;i<=k;++i){
if(!f[x][i])break;
sum[i]=(sum[i]+f[x][i])%mod;
}
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==fa)continue;
for(int j=0;j<=k;++j)tmp[j]=(f[x][j]-g[v][j]+mod)%mod;
for(int j=k;j;--j)tmp[j]=(1ll*tmp[j-1]*j%mod+tmp[j])%mod;
for(int j=0;j<=k;++j)f[v][j]=(f[v][j]+tmp[j])%mod;
dfs_get(v,x);
}
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<n;++i){int u,v;scanf("%d%d",&u,&v);add(u,v);add(v,u);}
pre();
dfs(1,0);
dfs_get(1,0);
int ans=0;
for(int i=0;i<=k;++i)ans=(ans+1ll*s[k][i]*sum[i]%mod)%mod;
ans=1ll*ans*inv_2%mod;
printf("%d\n",ans);
return 0;
}
D. 树
神仙分块题。
首先\(dfs\)是肯定的,转化为区间问题
暴力分比较好拿,对每个深度的点建线段树即可。(实际上\(vector\)+二分即可)
正解需要离线
首先所有修改可以转化为修改某个区间所有深度\(dep\%x=y^`\)的点
对于模数\(x<=\sqrt n\)和\(x>\sqrt n\)的修改分开处理
先说\(x<\sqrt n\)的
首先枚举\(x\)共\(\sqrt n\)个
我们需要保证的最终复杂度是\(O(q\sqrt n)\)
注意到询问操作需要进行\(q\sqrt n\)次,那么要保证每次查询是\(o(1)\)的
而修改操作需要\(1\)次,分块处理复杂度就可以保证了
\(f[pos][res]\)记录第\(pos\)块余数为\(res\)的点需要加上的权值,这样对不同余数分开处理
不是整块怎么办?点的深度是确定的,所以只用开一维记录,修改时候判一下该点是否为需要修改的点即可
然后\(x>\sqrt n\)的
你会发现每次修改最多\(\sqrt n\)个深度,考虑最开始暴力的想法,对每个深度分别处理
为了保证复杂度为\(O(q\sqrt n)\)那么修改操作必须\(O(1)\)完成,那么自然需要使用差分
查询时候搞一下前缀和(分块实现),这样总复杂度保证\(O(q\sqrt n)\)
但是发现直接这样搞数组开不下,那么每次处理\(\sqrt n\)个深度的操作,分\(\sqrt n\)次进行,
分块数组一次只能处理一个深度,所以把有关这\(\sqrt n\)个深度的操作分开存一下,一次处理一个深度
注意不能直接一个一个深度处理,那样复杂度是\(O(nq)\)的(因为每个操作被扫到了\(n\)次)(虽然数据可以水过去)
code
#include <cstring>
#include <vector>
#include <cstdio>
#include <cmath>
using namespace std;
int min(int x,int y){return x<y?x:y;}
const int maxn=300005;
const int gm=605;
struct edge{
int net,to,val;
}e[maxn<<1|1];
int head[maxn],tot;
void add(int u,int v){
e[++tot].net=head[u];
head[u]=tot;
e[tot].to=v;
}
int n,len,q;
struct node{
int dep,fa,dfsl,dfsr;
}d[maxn];
int id[maxn];
int tmp,maxdep;
void dfs(int x){
d[x].dfsl=++tmp;id[tmp]=x;
for(int i=head[x];i;i=e[i].net){
int v=e[i].to;
if(v==d[x].fa)continue;
d[v].fa=x;d[v].dep=d[x].dep+1;
if (d[v].dep>maxdep)maxdep=d[v].dep;
dfs(v);
}
d[x].dfsr=tmp;
}
struct ask{
int l,r,x,y,z,op,pos;
}ak[maxn];
int fk[gm][gm],yk[maxn],pos[maxn],ans[maxn];
void work1(){
for(int mod=1;mod<=len;++mod){
for(int i=1;i<=q;++i){
if(ak[i].op==2){
ans[i]+=fk[pos[ak[i].pos]][d[id[ak[i].pos]].dep%mod]+yk[ak[i].pos];
continue;
}
if(ak[i].x!=mod)continue;
if(pos[ak[i].l]==pos[ak[i].r]){
for(int l=ak[i].l;l<=ak[i].r;++l)
if(ak[i].y==d[id[l]].dep%mod)yk[l]+=ak[i].z;
}else{
int ls=pos[ak[i].l]*len;
for(int l=ak[i].l;l<=ls;++l)
if(ak[i].y==d[id[l]].dep%mod)yk[l]+=ak[i].z;
ls=pos[ak[i].r]*len-len+1;
for(int l=ls;l<=ak[i].r;++l)
if(ak[i].y==d[id[l]].dep%mod)yk[l]+=ak[i].z;
for(int l=pos[ak[i].l]+1;l<pos[ak[i].r];++l)
fk[l][ak[i].y]+=ak[i].z;
}
}
for(int i=1;i<=q;++i){
if(ak[i].op==2||ak[i].x!=mod)continue;
if(pos[ak[i].l]==pos[ak[i].r]){
for(int l=ak[i].l;l<=ak[i].r;++l)
if(ak[i].y==d[id[l]].dep%mod)yk[l]-=ak[i].z;
}else{
int ls=pos[ak[i].l]*len;
for(int l=ak[i].l;l<=ls;++l) if(ak[i].y==d[id[l]].dep%mod) yk[l]-=ak[i].z;
ls=pos[ak[i].r]*len-len+1;
for(int l=ls;l<=ak[i].r;++l) if(ak[i].y==d[id[l]].dep%mod) yk[l]-=ak[i].z;
for(int l=pos[ak[i].l]+1;l<pos[ak[i].r];++l) fk[l][ak[i].y]-=ak[i].z;
}
}
}
}
int cf[maxn],ck[gm];
struct ll{
bool op;
int l,r,z;
ll(){}
ll(int _l,int _r,int _z){
op=1;
l=_l;
r=_r;
z=_z;
}
ll(int pos,int id){
op=0;
l=pos;
r=id;
}
};
vector<ll>v[gm];
void add(int l,int r,int v){
cf[l]+=v;cf[r+1]-=v;
ck[pos[l]]+=v;ck[pos[r+1]]-=v;
}
int query(int posi){
int la=0;
for(int l=1;l<pos[posi];++l)la+=ck[l];
for(int l=pos[posi]*len-len+1;l<=posi;++l)la+=cf[l];
return la;
}
void work2(){
for(int dl=1;dl<=maxdep;dl+=len){
int dr=min(maxdep,dl+len-1);
for(int i=1;i<=q;++i){
if(ak[i].op==1){
if(ak[i].x<=len)continue;
int kl=ceil((1.0*dl-ak[i].y)/ak[i].x);
int kr=floor((1.0*dr-ak[i].y)/ak[i].x);
for(int k=kl;k<=kr;++k){
int dep=ak[i].y+ak[i].x*k;
v[dep-dl].push_back(ll(ak[i].l,ak[i].r,ak[i].z));
}
}else{
int dep=d[id[ak[i].pos]].dep;
if(dep<dl||dep>dr)continue;
v[dep-dl].push_back(ll(ak[i].pos,i));
}
}
for(int i=dl;i<=dr;++i){
int s=v[i-dl].size();
for(int j=0;j<s;++j)
if(v[i-dl][j].op)add(v[i-dl][j].l,v[i-dl][j].r,v[i-dl][j].z);
else ans[v[i-dl][j].r]+=query(v[i-dl][j].l);
for(int j=0;j<s;++j)
if(v[i-dl][j].op)add(v[i-dl][j].l,v[i-dl][j].r,-v[i-dl][j].z);
v[i-dl].clear();
}
}
}
int main(){
scanf("%d%d",&n,&q);
len=sqrt(n);for(int i=1;i<=n;++i)pos[i]=(i+len-1)/len;
for(int i=1;i<n;++i){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
d[1].dep=1;dfs(1);
for(int i=1;i<=q;++i){
scanf("%d",&ak[i].op);
if(ak[i].op&1){
int v,x,y,z;
scanf("%d%d%d%d",&v,&x,&y,&z);
ak[i].l=d[v].dfsl;
ak[i].r=d[v].dfsr;
ak[i].z=z;ak[i].x=x;
ak[i].y=(d[v].dep+y)%x;
}else{
int v;scanf("%d",&v);
ak[i].pos=d[v].dfsl;
}
}
work1();
work2();
for(int i=1;i<=q;++i)
if((ak[i].op&1)==0)
printf("%d\n",ans[i]);
return 0;
}