初二下 合集

TREES OF TRANQUILLITY

考虑第一棵树肯定是选整条链。把第二棵树的限制搞成dfs序区间,因为这种区间要么不相交要么完全覆盖,所以用线段树维护一下就好了。

具体地,把每个点的区间赋值成这个点,然后下面的点:

  • 如果发现自己[in,out]的位置已经有人了,那就把上面那个人干掉,自己加进去。
  • 如果不冲突,直接塞进去。
  • 如果自己覆盖了别人,那就不加自己了。

注意不要每次memset(t<=3e5),重新build的时候要把lazy标记清空!

#include<bits/stdc++.h>
using namespace std;
int t,n;
const int N=6e5+5;
vector<int> g[N],e[N];
int en[N],ot[N],cnt;
void dfs2(int u,int fa){
    int i;en[u]=++cnt;
    for(i=0;i<e[u].size();i++){
        int v=e[u][i];
        if(v!=fa)dfs2(v,u);
    }
    ot[u]=cnt;
}
int ans,res;
typedef long long ll;
struct node{
    int l,r;
    ll sum,lazy;
};
struct SegmentTree{
    node tr[1200005];
    void build(int k,int l,int r){
        tr[k].l=l,tr[k].r=r;
        if(l==r){
            tr[k].sum=0;
            tr[k].lazy=0;
            return ;
        }
        int mid=l+r>>1;
        build(k<<1,l,mid),build(k<<1|1,mid+1,r);
        tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
        tr[k].lazy=0;
    }
    void push_down(int k,int l,int r){
        tr[k<<1].lazy+=tr[k].lazy,tr[k<<1|1].lazy+=tr[k].lazy;
        tr[k<<1].sum+=tr[k].lazy*(tr[k<<1].r-tr[k<<1].l+1);
        tr[k<<1|1].sum+=tr[k].lazy*(tr[k<<1|1].r-tr[k<<1|1].l+1);
        tr[k].lazy=0;
    }
    void modifly(int k,int l,int r,int L,int R,ll v){
        if(l>=L&&r<=R){
            tr[k].sum+=(r-l+1)*v;
            tr[k].lazy+=v;
            return ;
        }
        push_down(k,l,r);
        int mid=l+r>>1;
        if(R<=mid)modifly(k<<1,l,mid,L,R,v);
        else if(L>mid)modifly(k<<1|1,mid+1,r,L,R,v);
        else modifly(k<<1,l,mid,L,mid,v),modifly(k<<1|1,mid+1,r,mid+1,R,v);
        tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
    }
    ll query(int k,int l,int r,int L,int R){
        if(l>=L&&r<=R){return tr[k].sum;}
        push_down(k,l,r);
        int mid=l+r>>1;
        ll s=0;
        if(R<=mid)s=query(k<<1,l,mid,L,R);
        else if(L>mid)s=query(k<<1|1,mid+1,r,L,R);
        else s=query(k<<1,l,mid,L,mid)+query(k<<1|1,mid+1,r,mid+1,R);
        return s;
    }
}t1,t2;
void dfs1(int u,int fa){
    int i;
    ll s=t1.query(1,1,n,en[u],ot[u]);
    ll t=t2.query(1,1,n,en[u],ot[u]);
    ll f1=0,f2=0;
    if(s){
    	if(ot[u]-en[u]+1==s){
    		ll w=t/s;
	        t1.modifly(1,1,n,en[w],ot[w],-1ll);
	        t2.modifly(1,1,n,en[w],ot[w],-w);
	        f1=w;
	        t1.modifly(1,1,n,en[u],ot[u],1ll);
	        t2.modifly(1,1,n,en[u],ot[u],u);
	        f2=u;
		}
    }else{
        t1.modifly(1,1,n,en[u],ot[u],1ll);
        t2.modifly(1,1,n,en[u],ot[u],u);
        f2=u;ans++;
    }  
    for(i=0;i<g[u].size();++i){int v=g[u][i];if(v!=fa)dfs1(v,u);}
    res=max(res,ans);
    if(f1&&f2){
        t1.modifly(1,1,n,en[f1],ot[f1],1ll),t2.modifly(1,1,n,en[f1],ot[f1],f1);
        t1.modifly(1,1,n,en[f2],ot[f2],-1ll),t2.modifly(1,1,n,en[f2],ot[f2],-f2);
    }else if(f2)
    t1.modifly(1,1,n,en[f2],ot[f2],-1ll),t2.modifly(1,1,n,en[f2],ot[f2],-f2),ans--;
}
int main(){
    int i;
    cin>>t;
    while(t--){
        scanf("%d",&n);
        int i;
        for(i=1;i<=n;++i)g[i].clear(),e[i].clear();
        cnt=0,res=0,ans=0;
        for(i=2;i<=n;++i){
            int x;
            scanf("%d",&x);
            g[x].push_back(i),g[i].push_back(x);
        }
        for(i=2;i<=n;++i){
            int x;
            scanf("%d",&x);
            e[x].push_back(i),e[i].push_back(x);
        }
        t1.build(1,1,n),t2.build(1,1,n);
		dfs2(1,0),dfs1(1,0);
        printf("%d\n",res);
    }
    return 0;
}

YSOI2020 换寝室

一开始不知道树形dp怎么搞,后面看了cxy题解之后恍然大悟。

二分极差,判断能否使代价小于等于k

f[i][j]:以i为根的子树,i所在的连通块值域为[aj,aj+x]

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int N=2005;
const int M=1000005; 
int cnt,nxt[N],to[N],h[N];
int a[N],d[N],b[M][2];
void add(int x,int y){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y;
}
typedef long long ll;
int f[N][15],dep[N];
ll s[N],dp[N][N];
void dfs(int u,int fa){
	f[u][0]=fa,dep[u]=dep[fa]+1;int i;
	for(i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa)dfs(v,u);
	}
}
int lca(int x,int y){
	if(dep[x]>dep[y])swap(x,y);int i;
	for(i=10;i>=0;i--)if(dep[f[y][i]]>=dep[x])y=f[y][i];
	if(x==y){return x;}
	for(i=10;i>=0;i--)if(f[y][i]!=f[x][i])y=f[y][i],x=f[x][i];
	return f[x][0];
}
void dfs2(int u,int fa){
	int i;s[u]=1ll*d[u];
	for(i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa)dfs2(v,u),s[u]+=s[v];
	}
}
ll qz[N][N],hz[N][N];
void dfs3(int u,int fa,int x){
	int i,j;//f[i][j]=sgm(min(f[son][!j]+edge(u,v),f[son][j]))
	for(i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa){
			dfs3(v,u,x); qz[v][0]=1e9+1,hz[v][n+1]=1e9+1;
			for(j=1;j<=n;j++)qz[v][j]=min(qz[v][j-1],dp[v][j]);
			for(j=n;j>=1;j--)hz[v][j]=min(hz[v][j+1],dp[v][j]);
			for(j=1;j<=n;j++)
				dp[u][j]+=min(min(qz[v][j-1],hz[v][j+1])+s[v],dp[v][j]);
		}
	}
	for(int j=1;j<=n;j++)
		if(!(a[u]>=a[j]&&a[u]<=a[j]+x))dp[u][j]=1e9+1;
}
int check(int x){
	memset(dp,0,sizeof(dp));
	memset(qz,0,sizeof(qz));
	memset(hz,0,sizeof(hz));
	dfs3(1,0,x);
	for(int i=1;i<=n;i++)
		if(dp[1][i]<=k){return 1;}
	return 0;
}
int main(){
	register int i,j;
	cin>>n>>m>>k;
	for(i=1;i<=n;++i)scanf("%d",&a[i]);
	for(i=1;i<n;++i){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	} 
	dfs(1,0);
	for(j=1;j<=10;++j)for(i=1;i<=n;++i)f[i][j]=f[f[i][j-1]][j-1];
	for(i=1;i<=m;++i){
		scanf("%d%d",&b[i][0],&b[i][1]);
		d[b[i][0]]++,d[b[i][1]]++;
		d[lca(b[i][0],b[i][1])]-=2;
	}
	dfs2(1,0);
	int l=0,r=1e9,ans;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid))ans=mid,r=mid-1;
		else l=mid+1;
	} 
	cout<<ans<<endl;
	return 0;
} 

NOI2021 题解/同步赛游记

一开始以为挺难的,还是犯了畏难的老毛病,一定要杜绝

不要给我面向数据编程

轻重边

70分,容易发现,如果a-b 存在,一定有 $ dfn[lst[a]]==dfn[lst[b]]$ 即最后一次操作到a,b的时间戳相同

100分

也就是说,a-b 路径上的连续段,都要满足上述条件。不断做区间覆盖,将一段路颜色改成当前时间,询问也就是统计颜色段个数。

所以场上有人拉了[SDOI 染色]的板子,过了大样例

不得不说,这个性质太妙了

庆典

对于这个特殊的有向图,可以对其尝试缩点,发现一定会缩成外向树。

k<2的64分就搞定了

k=2,场上我是大力分讨的,但后面因为情况太多弃了

量子通信

256=16*16,进行分块,而k<=15,所以肯定有一个整块要完全相同,然而数据均匀随机,所以相同的16×400000/65536
差不多100个,对于这些串,暴力bitset判断即可

剩下的题都不会了,我还是太菜了

这篇游记,可以说是自CSP2019之后写的字数最多的了

Day1

晚上没睡好。

早上一大早挣扎着起来,发现没开???

睡了一会,tm睡着了。

开考20分钟的时候才醒

中间还浪费了不少时间,总共加起来有一个多小时

T1

50分不是傻逼吗,想想70

不会啊70

很久以后才决定写50

中间爆栈了,我还调了很久,日!

调了整整3h啊啊啊啊

T2

看错题了www

白耗了2h,fuck

0分,日!

T3

1-7 都是傻逼

但好像一颗外向树的k=2有点难写

只能拿44分

算了,只剩25分钟了,老子不写了

就28分得了,日!

怎么说呢,T1 100分不会做的确是我能力不够,但是70分真的是水得不能再水了,为啥我B性质拿不到呢???

赛后重新看了一眼2

原来我能写n<=10且有B性质的40分,日!

为什么就没想到状压,然后把每层答案乘起来呢???

T3缩点之后有这么好的性质,我也没想到

最近脑子一直不清醒啊,必须赶紧把状态调整过来!

50+0+28=78

Day 2

T1

做法很早就想到了,但是没看到数据随机,以为自己是暴力分,而且恰好空间开小了导致第3个大样例过不了,就一直以为自己不会做。

(后来问了一位神仙才发现自己原来是对的)

然后迅速切掉了

T2

不会做啊,完全不会做啊啊啊啊啊啊

T3

一开始有点思路,后来一直调不出来,才发现假掉了。。。

100+0+5=105

事实证明面向数据编程会想错

真正的强者,一定是脑子中不断冒出做法,深度思考后,再去看自己能过几分的

我:78+88=166

人均:70+40+44+100+20+36=310

神仙:100+100+100+100+70+100=570

看到了自己与众人的差距


EZEC-10 序列

难道就没有人发现这题是二合一吗?

考虑 \(X\ \operatorname{xor}\ Y = Z\)

等价于 \(X\ \operatorname{xor}\ Z = Y\)

等价于 \(Y\ \operatorname{xor}\ Z = X\)

于是建图,连(x,y,z)的无向边。BFS,对于每个连通块,如果一路异或到某个点的Z有两种,那显然矛盾,puts("0")退出

从rt点BFS时,把扩展到的每个点进行一遍可乐,求出rt点值的取值区间,乘起来就行。

但是值域有1e9,求出取值区间需要差分+离散化,但是这样就2log了。所以直接基数排序。

(前面写的常数太大,拿cxy的代码调了一下午)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mod 1000000007
#define N 500005
int n,m,K,cnt,h[N<<1],nxt[N<<1],to[N<<1],val[N<<1],yh[N]; 
inline void add(int x,int y,int z){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y,val[cnt]=z; 
}
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    char ch=nc();
    int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=(sum<<1)+(sum<<3)+(ch^48),ch=nc();
    return sum;
}
queue<int> q;
int tot,vis[N],p;
int R[256];
struct node{
	int x,y;
	bool operator <(const node u)const{
		return x<u.x;
	} 
}P[20000005],t[20000005]; 
ll ans=1;
inline void cola(int v){
    int cur=0;
	register int i;
    for(i=29;i>=0;--i){
    	if((K>>i)&1){
            int tmp=!((v>>i)&1);
            tmp=tmp<<i,cur+=tmp;
    	}else{
    		int tmp=!((v>>i)&1);
    		tmp=tmp<<i;
    		t[++p]=(node){cur+tmp,1};
    		t[++p]=(node){cur+tmp+(1<<i),-1};
    		tmp=(v>>i)&1,tmp=tmp<<i,cur+=tmp;
    	}
    }
}
inline void bfs(int x){
	q.push(x);
	register int i,u,v,w;
	vis[x]=1,yh[x]=0;cola(0);
	while(!q.empty()){
		u=q.front();q.pop();
		for(i=h[u];i;i=nxt[i]){
			v=to[i],w=val[i];
			if(!vis[v])yh[v]=yh[u]^w,vis[v]=1,tot++,cola(yh[v]),q.push(v);
			else if(yh[v]!=(yh[u]^w)){
				puts("0");
				exit(0);
			}
		}
	}
}
inline void radix_sort(){
	register int i;
	for(i=1;i<=p;++i)R[t[i].x&255]++;
    for(i=1;i<=255;++i)R[i]+=R[i-1];
    for(i=p;i>=1;--i)P[R[t[i].x&255]--]=t[i];
    for(i=1;i<=p;++i)t[i]=P[i];
    for(i=0;i<=255;++i)R[i]=0;
	for(i=1;i<=p;++i)R[(t[i].x>>8)&255]++;
    for(i=1;i<=255;++i)R[i]+=R[i-1];
    for(i=p;i>=1;--i)P[R[(t[i].x>>8)&255]--]=t[i];
    for(i=1;i<=p;++i)t[i]=P[i];
    for(i=0;i<=255;++i)R[i]=0;
	for(i=1;i<=p;++i)R[(t[i].x>>16)&255]++;
    for(i=1;i<=255;++i)R[i]+=R[i-1];
    for(i=p;i>=1;--i)P[R[(t[i].x>>16)&255]--]=t[i];
    for(i=1;i<=p;++i)t[i]=P[i];
    for(i=0;i<=255;++i)R[i]=0;
	for(i=1;i<=p;++i)R[(t[i].x>>24)&255]++;
    for(i=1;i<=255;++i)R[i]+=R[i-1];
    for(i=p;i>=1;--i)P[R[(t[i].x>>24)&255]--]=t[i];
    for(i=1;i<=p;++i)t[i]=P[i];
    for(i=0;i<=255;++i)R[i]=0;
}
int res;
int main(){
	n=read(),m=read(),K=read();
	register int i,j,x,y,z;
	for(i=m;i;--i){
		x=read(),y=read(),z=read();
		add(x,y,z),add(y,x,z);
	}int tt;
	for(i=1;i<=n;++i){
		if(vis[i])continue;
		tot=p=res=tt=0;
		bfs(i);t[++p]=(node){0,0};
		if(p<=512)sort(t+1,t+p+1);
		else radix_sort();
		for(j=1;j<=p;++j){
	        tt+=t[j].y;
	        if(tt==0){
	        	if(j!=p)res+=t[j+1].x-t[j].x;
	        	else res+=1073741823-t[j].x+1;
	        }
	    }
		ans=ans*(1ll*res)%mod;
	}
	printf("%lld",ans);
	return 0;
}

牛客挑战赛51

A sbt,不讲

B 巧妙的并查集

很显然有一种贪心做法,每次都尽量从最大的[E]集合加到第二大的集合

C 数位dp+二分

二分一下y,用数位dp判定[x,y]中是否有>=k个回文数

D 不会

E 好题

https://ac.nowcoder.com/acm/contest/11191/E

考虑通过欧拉函数拆开来,统计一下三元环的贡献i就行了。

挑战赛 ×

数学赛 √

看来Math-Round的质量还挺高的嘛

牛客周赛26

只会A www

A

手玩几遍,发现交换两个数奇偶性不变,剩下三个操作直接拆解掉就行了,SBT

B

想到一个比较和谐的性质,但还是不会DP啊。。。

先咕着,之后再写


BZOJ4771七彩树

不是原题,是LJ考试一道类似的题

给定一棵以结点1为根的树,每个点有颜色\(C_i\)。每次询问给定u,d,你要求出在以u为根的子树
内所有与u距离不超过d的点中不同颜色个数,强制在线。

如果没有距离限制,考虑如何计算答案(不用数据结构方法)

考虑每两个颜色相同的点U,V,lca(U,V)=W,能对哪些点答案产生贡献。显然,\(b_u++,b_v++,b_w --\) 但这么做会算重,所以对于每个U只计算它的前驱、后继作为V的贡献,具体地:

nik:版本,fk:U

update(root[nik],root[nik-1],1,1,n,dfn[fk]);

if(pre)nik++,update(root[nik],root[nik-1],-1,1,n,dfn[lca(pre,fk)]);

if(suc)nik++,update(root[nik],root[nik-1],-1,1,n,dfn[lca(fk,suc)]);

if(pre&&suc)nik++,update(root[nik],root[nik-1],1,1,n,dfn[lca(pre,suc)]);

最后点x子树的b和就是x点的答案。

但是有了距离限制,容易想到用bfs序来一个个加入节点到新主席树里,最后记录下每层最右边的那个节点的主席树版本号。

查询时,直接找U对应的版本的\(Sum[in[u],out[u]]\)

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=200005;
int cnt,h[N*2],nxt[N*2],to[N*2];
void add(int x,int y){
	cnt++;
	nxt[cnt]=h[x];
	h[x]=cnt;
	to[cnt]=y;
}
queue<int> q;
int bh[N],tot,rh[N],c[N],vis[N],dep[N],dfn[N],ot[N];
int f[N][21];
void init(){
	memset(vis,0,sizeof(vis));
	q.push(1),vis[1]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		tot++;bh[tot]=u,rh[u]=tot;
		for(int i=h[u];i;i=nxt[i]){
			int v=to[i];
			if(!vis[v])vis[v]=1,q.push(v);
		}
	}
} 
int tim,mxd;
void dfs(int u,int fa){
	dfn[u]=++tim;
	f[u][0]=fa;dep[u]=dep[fa]+1;mxd=max(mxd,dep[u]);
	for(int i=h[u];i;i=nxt[i]){
		int v=to[i];
		if(v!=fa)dfs(v,u);
	}ot[u]=tim;
} 
struct node{
    int l,r,sum;
}T[N*40];
int root[N],pos[N];
void update(int &now,int pre,int val,int l,int r,int pos){
    T[++cnt]=T[pre],T[cnt].sum+=val,now=cnt;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) update(T[now].l,T[pre].l,val,l,mid,pos);
    else update(T[now].r,T[pre].r,val,mid+1,r,pos);
}
int query(int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r) return T[rt].sum;
    int ans=0,mid=l+r>>1;
    if(L<=mid)ans+=query(L,R,l,mid,T[rt].l);
    if(R>mid)ans+=query(L,R,mid+1,r,T[rt].r);
    return ans;
}
int lca(int x,int y){
	int i;if(dep[x]>dep[y])swap(x,y);
	for(i=20;i>=0;i--)if(dep[f[y][i]]>=dep[x])y=f[y][i];
	if(x==y)return x;
	for(i=20;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
set<pair<int,int> > t[N];
int nik,bil[N];
int main(){
	//freopen("cheese.in","r",stdin);
	//freopen("cheese.out","w",stdout);
	int tt;
	cin>>tt;
	while(tt--){
		scanf("%d%d",&n,&m);
		int i,j;cnt=0;nik=0,mxd=0,tot=0,tim=0,lastans=0;
		memset(to,0,sizeof(to)),memset(nxt,0,sizeof(nxt)),memset(h,0,sizeof(h));
		for(i=1;i<=n;i++)scanf("%d",&c[i]),t[i].clear();
		for(i=2;i<=n;i++){
			int x;scanf("%d",&x);
			add(x,i),add(i,x);
		}
		init();dfs(1,0);
		for(j=1;j<=20;j++)for(i=1;i<=n;i++)f[i][j]=f[f[i][j-1]][j-1];
		for(i=1;i<=n;i++){
			int fk=bh[i],pre=0,suc=0;++nik;
			pair<int,int> k=make_pair(dfn[fk],fk);
			update(root[nik],root[nik-1],1,1,n,dfn[fk]);
	        set<pair<int,int> >::iterator v=t[c[fk]].lower_bound(k);
			if(v!=t[c[fk]].end())suc=v->second;
			if(v!=t[c[fk]].begin())v--,pre=v->second;
	        if(pre)nik++,update(root[nik],root[nik-1],-1,1,n,dfn[lca(pre,fk)]);
	        if(suc)nik++,update(root[nik],root[nik-1],-1,1,n,dfn[lca(fk,suc)]);
	        if(pre&&suc)nik++,update(root[nik],root[nik-1],1,1,n,dfn[lca(pre,suc)]);
			bil[dep[fk]]=nik;t[c[fk]].insert(k);
	    }
		while(m--){
			int u,d;scanf("%d%d",&u,&d);
			u^=lastans,d^=lastans;
			int deep=min(dep[u]+d,mxd);
			lastans=query(dfn[u],ot[u],1,n,root[bil[deep]]);
			printf("%d\n",lastans);
		}
	} 
	return 0;
} 

EZEC8 猜书(交互)题解

第一篇交互题题解,就当是在学交互题格式罢

这道题2000的数据量,1e5个询问,不用说一定是根号分治。

具体的,对于第i层(设有x个点)第i-1层(有y个)的点,两两之间询问一遍距离,但如果树很扁就会挂掉,那就对于\(x > \sqrt n\)\(y > \sqrt n\)的那几层,上面的节点问一遍子树,盘掉就行了。易证这样询问数一定是不超过1e5的

通过这题,我发现IO交互要在每个printf后面加上fflush(stdout),没了。。。

#include<bits/stdc++.h>
using namespace std;
int n;
int dep[2005],depp,f[2005]; 
vector<int> depth[2005];
int read(){
	int s=0,w=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-')w=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')s=(s<<1)+(s<<3)+(c^48),c=getchar();
	return s*w;
} 
void ask1(int x,int y){
	printf("? 1 %d %d\n",x,y);
	fflush(stdout);
	int d=read();
	if(x==1)dep[y]=d,depth[d].push_back(y),depp=max(d,depp);
	if(d==1)f[y]=x;
}
void ask2(int u){
	printf("? 2 %d\n",u);
	fflush(stdout);
	int sz=read();
	while(sz--){
		int x=read();
		if(dep[u]==dep[x]-1)f[x]=u;
	}
}
int main(){
	n=read();
	int i,j,k;
	for(i=2;i<=n;i++)ask1(1,i);
	int w=sqrt(n);//根号分治 
	for(i=2;i<=depp;i++){
		int sz1=depth[i].size(),sz2=depth[i-1].size();
		if(sz1>w&&sz2>w){
			for(j=0;j<sz2;j++)ask2(depth[i-1][j]);
		}else{
			for(j=0;j<sz1;j++){
				for(k=0;k<sz2;k++){
					ask1(depth[i-1][k],depth[i][j]);
				}
			}
		}
	}
	putchar('!');
	for(i=2;i<=n;i++)printf(" %d",f[i]);
	fflush(stdout);
	return 0;
}

NTT学习笔记

呼呼呼,因为要学多项式,终于被逼着学会了\(LaTeX\)

  • [模板]多项式乘法

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    const int N=4000005;
    typedef long long ll;
    const ll P=998244353,g=3,gi=332748118;
    ll a[N],b[N];
    int lmt=1,L=0;
    int r[N];
    ll qp(ll x,ll y){
     ll res=1;
     while(y){
         if(y&1)res=(res*x)%P;
         x=(x*x)%P;
         y>>=1;
     }
     return res;
    }
    void NTT(ll *A,int tp){
     int i;
     for(i=0;i<lmt;i++)if(i<r[i])swap(A[i],A[r[i]]);
     for(int md=1;md<lmt;md<<=1){
         ll omega=qp(tp==1?g:gi,(P-1)/(md<<1)); 
         for(int j=0;j<lmt;j+=(md<<1)){
             ll w=1;
             for(int k=0;k<md;k++,w=(w*omega)%P){
                 ll x=A[j+k],y=w*A[j+k+md]%P;
                 A[j+k]=(x+y)%P,A[j+k+md]=(x+P-y)%P;
             }
         }
     }
    }
    int main(){
     int i;
     cin>>n>>m;
     for(i=0;i<=n;i++)scanf("%lld",&a[i]),a[i]=(a[i]%P+P)%P;
     for(i=0;i<=m;i++)scanf("%lld",&b[i]),b[i]=(b[i]%P+P)%P;
     while(lmt<=n+m)lmt<<=1,L++; 
     for(i=0;i<lmt;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(L-1));
     NTT(a,1),NTT(b,1);
     for(i=0;i<lmt;i++)a[i]=(a[i]*b[i])%P;
     NTT(a,-1);
     ll rv=qp(lmt,P-2);
     for(i=0;i<=n+m;i++)printf("%lld ",(a[i]*rv)%P);
     return 0;
    }
    

  
    你才知道我连FFT都没写过,直接背NTT模板的吗?
  
- **P6300 悔改**
 
 设ai为长度为i的木棍的出现次数,则易得
 $ Ans_k  = \sum_{i+j=k}^{n} \min(a_i,a_j)$
  
后面这个min函数有点烦,怎么办呢?

提一个d出来,算d作为最小值,对$Ans_k$的贡献
$ \sum_{d=1}^{n} \sum_{i+j=k} [a_i>=d][a_j>=d]$

枚举这个d,离散化之后,$ \sum_{d=1}^{} = n $,所以
d是$ \sqrt{n}$级别的 

于是,对于后面$\sum_{i+j=k}[a_i>=d][a_j>=d]$,这个直接NTT加容斥搞定了

代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=400005;
int a[N],r[N],b[N],limit=1,L=0;
typedef long long ll;
const ll P=998244353,g=3,gi=332748118;
ll c[N],rs[N];
inline int read(){
	ll s=0,f=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar(); }
	while(c<='9'&&c>='0')s=(s<<1)+(s<<3)+(c^48),c=getchar();
	return s*f;
} 
inline ll qp(ll x,ll y){
	ll res=1;
	while(y){
		if(y&1)res=(res*x)%P;
		x=(x*x)%P;
		y>>=1;
	} 
	return res;
}
inline void NTT(ll *u,int v,int len){
	for(int i=0;i<len;i++)if(i<r[i])swap(u[i],u[r[i]]);
	for(int mid=1;mid<len;mid<<=1){
		ll omega=qp(v==1?g:gi,(P-1)/(mid<<1));
		for(int j=0;j<len;j+=(mid<<1)){
			for(int k=0,w=1;k<mid;k++,w=(w*omega)%P){
				int x=u[j+k],y=w*u[j+k+mid]%P;
				u[j+k]=(x+y)%P,u[j+k+mid]=(x-y+P)%P;
			}
		}
	}
}
ll ans,fans;
int main(){
	register int i,j;
	n=read(),m=read();
	for(i=1;i<=n;++i)a[read()]++;
	for(i=1;i<=m;++i)b[i]=a[i];
	sort(b,b+m+1);
	int q=unique(b,b+m+1)-b-1;
	while(limit<=2*m)limit*=2,L++;
	for(i=0;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(L-1));
	for(i=1;i<=q;++i){
		int w=b[i];
		c[0]=0;
		for(j=1;j<limit;++j){
			if(a[j]>=w)c[j]=1;
			else c[j]=0;
		}
		NTT(c,1,limit);
		for(j=0;j<limit;++j)c[j]=c[j]*c[j]%P;
		NTT(c,-1,limit);
		ll inv=qp(limit,P-2);
		for(j=1;j<=m*2;++j)
			rs[j]+=1ll*(b[i]-b[i-1])*(c[j]*inv%P)%P;
	}
	for(i=1;i<=m*2;++i){
		if(rs[i]/2>ans){
			ans=rs[i]/2;
			fans=i;
		}
	}
	printf("%lld %lld\n",ans,fans);
	return 0;
} 
  • 牛客OJ NC220167 简单题

调了一万年,至今未AC...

给出两个长度为n的非负整数序列\(a_1,a_2,\cdots ,a_n\)
\(b_1,b_2,\cdots,b_n\) ,对每个 \(k\in[1,3n]\)\(C_k=\sum_{i+j+gcd(i,j)=k}a_ib_j\)

结果对998244353取模。

考虑计算每个d作为\(gcd(i,j)\) ,对各个k的贡献

仔细推一波,容斥一下

(感觉好多NTT题都要容斥啊)

  • P5641 【CSGRound2】开拓者的卓识

反正算贡献了之后,推一波柿子就行了

\(A_i=a_i*C(i+k-1,k-1),Bi=C(i+k-1,k-1)\)
会发现柿子可以化成\(\sum_{i+j=k}A_iB_j\)的形式

然后就是sbt了,代码:

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=4e5+5;
typedef long long ll;
ll k,a[N];
const ll P=998244353,g=3,gi=332748118;
ll b[N],c[N];
ll qp(ll x,ll y){
	ll res=1;
	while(y){
		if(y&1)res=(res*x)%P;
		x=(x*x)%P;
		y>>=1;
	}
	return res;
} 
ll lmt=1,L=0,r[N];
void NTT(ll *A,int ty){
	int i;
	for(i=0;i<lmt;i++)if(i<r[i])swap(A[i],A[r[i]]);
	for(ll mid=1;mid<lmt;mid<<=1){
		ll omega=qp(ty==1?g:gi,(P-1)/(mid<<1));
		for(int j=0;j<lmt;j+=mid<<1){
			ll w=1;
			for(ll k=0;k<mid;k++,w=(w*omega)%P){
				ll x=A[j+k],y=w*A[j+k+mid];
				A[j+k]=(x+y)%P,A[j+k+mid]=(x+P-y)%P;
			}
		}
	}
}
int main(){
	int i;
	cin>>n>>k;
	c[0]=1;
	for(i=1;i<n;i++)c[i]=c[i-1]*(i+k-1)%P*qp(i,P-2)%P;
	for(i=0;i<n;i++){
		scanf("%lld",&a[i]);
		b[i]=c[i]*a[i]%P;
	}
	while(lmt<=n+n)lmt*=2,L++;
	for(i=0;i<lmt;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(L-1));
	NTT(b,1),NTT(c,1);
	for(i=0;i<lmt;i++)b[i]=b[i]*c[i]%P;
	NTT(b,-1);
	ll inv=qp(lmt,P-2);
	for(i=0;i<n;i++)printf("%lld ",(b[i]*inv%P+P)%P); 
	return 0; 
} 

PMOI-3 题解

人太菜了,以后周2,4,7要补一点比赛题,写题解

T1 sbt

T3 dp+mobius

f[i][j]表示当前填到第i个数,填j的方案数。(n^2*m)

考虑到可以反演把gcd搞掉,同时xi互不相同,O(n*m)

把后面一坨式子用pr记下来,O(nlogm^2)

最后迪利克雷搞一搞前后缀和,可以搞到100pts

参考赛后题解

80pts

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=500005;
int pr[N],f[N],x[N];
int mu[N],pri[N],cnt,vis[N];
const int mod=998244353;
vector<int> g[N];
void init(){
	int i,j;
	mu[1]=1;
	for(i=2;i<=m;i++){
		if(!vis[i]){
			pri[++cnt]=i;
			mu[i]=-1;
		}
		for(j=1;j<=cnt&&pri[j]<=m/i;j++){
			vis[i*pri[j]]=1;
			if(i%pri[j]==0){
				mu[i*pri[j]]=0;
				break;
			}
			mu[i*pri[j]]=-mu[i];
		}
	}
	for(i=1;i<=m;i++){
		for(j=i;j<=m;j+=i){
			g[j].push_back(i);
		}
	}
}
int main(){
	int i,j,k;
	cin>>n>>m;
	for(i=1;i<n;i++)scanf("%d",&x[i]);
	init();
	for(i=x[1];i<=m;i+=x[1]){
		for(j=0;j<g[i].size();j++)pr[g[i][j]]++;
	}
	for(i=1;i<n;i++){
		for(j=1;j<=m/x[i];j++){
			for(k=0;k<g[j].size();k++){
				int w=g[j][k];
				f[j*x[i]]=(f[j*x[i]]+mu[w]*pr[w*x[i]])%mod;
			}
		}
		for(j=1;j<=m/max(1,x[i-1]);j++){
			for(k=0;k<g[max(x[i-1],1)*j].size();k++){
				pr[g[max(x[i-1],1)*j][k]]=0;
			}
		} 
		for(j=x[i];j<=m;j+=x[i]){
			for(k=0;k<g[j].size();k++){
				pr[g[j][k]]=(pr[g[j][k]]+f[j])%mod;
			}
			if(i!=n-1)f[j]=0;
		}
	}
	int ans=0;
	for(i=1;i<=m;i++)ans=(ans+f[i])%mod;
	cout<<ans<<endl;
	return 0;
} 

T5:

很显然 ans=sum(a,b){e<F(L,d)-F(L,c)<f}

考虑将[b,c]段提出来,指针p b->a 移动,F(L,d)-F(L,c)是单调不增的,然后就可以二分

当F(L,d)-F(L,c)=e或f时,p可以到哪里。log^2主席树解决

ZJOI2021划水记

一般都是考完之后写的,这次看了1e4-1的游记,学他没考就写

NOIP参加了,分也不低,但是CSP分太低了所以只能是非正式。

因为初三有几个人想参加,但学校只有5个名额,我和5ab是到4月8日才被加进去的。希望别给学校丢脸吧。(话说最近比赛好多啊,又可以去boom0了呢,像NOIO一样)

Day -2(4.8) 来机房颓废,写作业,希望感冒赶快好。

Day -1 will赶作业

Day 1:

感冒没好,状态差得一匹

T1 没想到log做法,大概40-60

T2 m=2没想到 30

T3 16

不到三位数

Day 2:

感冒还没好。

T1 最后20mins写的,只写了30,早知道这题部分分那么好拿就去打了

T2 花了3h都没把2n×n×m的状压调好,就写了2n×n^2×m,mmp只能过80,全排列tm都能60

T3 写都没写

Day 3:

测了下冥间数据

最好情况也就200上下,不管了,搞whk去了,马上要期中考了希望能好些

送自己一句话:不要害怕任何题。

人之所以能,是相信能


posted @ 2023-07-14 14:05  Anticipator  阅读(29)  评论(0)    收藏  举报