CSP集训题解

CSP集训题解

Summer已经完结了于是新开一个,而且旧的已经很卡了

9.13CSP-S模拟5

T1 F

比较水的一眼题,先没看题观察数据范围发现是n方的,读题发现显然可能合法的x只有\(O(n)\)个,就是拿\(a_1\)和所有的b异或一遍就行了,别的x既然\(a_1\)都异或不出来那显然不可能,对于一个数,它异或另一个数能得到x的话,那么异或的那个数显然是唯一的,于是随便拿个map或者01Trie维护一下剩下的值就行了。我开始还以为它要算合法的排列数

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=2e3+10;

int a[maxn],b[maxn];
vector<int>vec,Ans;
int n;

void Check(int x){
	map<int,int>Mp;
	Rep(i,1,n)Mp[b[i]]++;
	Rep(i,1,n){
		if(Mp.find(a[i]^x)==Mp.end())return;
		if(Mp[a[i]^x]>0)Mp[a[i]^x]--;
		else return;
	}
	Ans.push_back(x);
}

void solve(){
	fre(f);
	cin>>n;
	Rep(i,1,n)cin>>a[i];Rep(i,1,n)cin>>b[i];
	Rep(i,1,n)vec.push_back(a[1]^b[i]);
	sort(vec.begin(),vec.end());vec.erase(unique(vec.begin(),vec.end()),vec.end());
	for(auto it : vec)Check(it);
	cout<<Ans.size()<<"\n";
	for(auto it : Ans)cout<<it<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T2 S

赛时打了个假dp连暴力分都不如。正解就是枚举前边三种颜色各放了几个,下一个位置要放啥转移,放的时候显然是贪心的拿最近的那个算一下距离就行。

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=4e2+2,INF=20051107;

int f[2][maxn][maxn][3];
bool vis[maxn][maxn];


int n;
char s[maxn];
int a[maxn];
int posr[maxn],posg[maxn],posy[maxn];
int cnt[maxn][3];

void solve(){
	fre(s20);
	cin>>n;cin>>(s+1);
	Rep(i,1,n){
		if(s[i]=='R')posr[++posr[0]]=i;
		if(s[i]=='G')posg[++posg[0]]=i;
		if(s[i]=='Y')posy[++posy[0]]=i;
		cnt[i][0]=posr[0],cnt[i][1]=posg[0],cnt[i][2]=posy[0];
	}
	int now=1;memset(f,0x3f,sizeof(f));
	f[1][1][0][0]=posr[1]-1;
	f[1][0][1][1]=posg[1]-1;
	f[1][0][0][2]=posy[1]-1;
	for(int i=1;i<n;++i){
		memset(f[now^1],0x3f,sizeof(f[now^1]));
		for(int x=0;x<=posr[0];++x){
			if(x>i)break;
			for(int y=0;y<=posg[0];++y){
				if(x+y>i)break;
				int z=i-x-y;
				int dx=posr[x+1]-i-1+max(0,y-cnt[posr[x+1]][1])+max(0,z-cnt[posr[x+1]][2]);
				int dy=posg[y+1]-i-1+max(0,x-cnt[posg[y+1]][0])+max(0,z-cnt[posg[y+1]][2]);
				int dz=posy[z+1]-i-1+max(0,x-cnt[posy[z+1]][0])+max(0,y-cnt[posy[z+1]][1]);
				if(x<posr[0])f[now^1][x+1][y][0]=min({f[now^1][x+1][y][0],f[now][x][y][1]+dx,f[now][x][y][2]+dx});
				if(y<posg[0])f[now^1][x][y+1][1]=min({f[now^1][x][y+1][1],f[now][x][y][0]+dy,f[now][x][y][2]+dy});
				if(z<posy[0])f[now^1][x][y][2]=min({f[now^1][x][y][2],f[now][x][y][0]+dz,f[now][x][y][1]+dz});
			}
		}
		now^=1;
	}
	cout<<min({f[now][posr[0]][posg[0]][0],f[now][posr[0]][posg[0]][1],f[now][posr[0]][posg[0]][2]});
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T3 Y

所有人都给其后边的人一个球的话,那么相当于没给,所以可以所有人都少给一个球,直到有一个人没有给后边的人球,那么环就可以从此断成链,转移是比较显然的,发现我们断链就是枚举一个起点,从起点推到n,再从1推回这个起点,这两部分内,对于每个i,每次的转移系数都是相同的,于是可以矩阵维护,每次乘一个前后缀矩阵就行了。

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=2e6+10,Mod=1e9+7,inv2=500000004,inv6=166666668;

int n,a[maxn];
ll ans;
//int f[maxn][maxn*10];
#define pre(x) ((x+n-1)%n)
#define nxt(x) ((x+1)%n)

struct Matrix{
	static const int N=2;
	int a[3][3];
	Matrix(){memset(a,0,sizeof(a));}
	void Clear(){memset(a,0,sizeof(a));}
	void Init(){Rep(i,1,N)a[i][i]=1;}
	Matrix operator*(const Matrix &x)const{
		Matrix z;
		for(int i=1;i<=N;i++){
			for(int k=1;k<=N;k++){
				int r=a[i][k];for(int j=1;j<=N;j++)z.a[i][j]=(1LL*x.a[k][j]*r%Mod+z.a[i][j])%Mod;
			}
		}
		return z;
	}
}Mu[maxn],PreMu[maxn],Beg[maxn];

void solve(){
	cin>>n;
	Rep(i,1,n)cin>>a[i];
	reverse(a+1,a+n+1);
	Rep(i,1,n)a[i+n]=a[i];
	Rep(i,1,n){
		Mu[i].a[1][1]=1LL*a[i]*(a[i]+1)%Mod*inv2%Mod;
		Mu[i].a[1][2]=((1LL*a[i]*Mu[i].a[1][1]%Mod-1LL*a[i]*(a[i]+1)%Mod*(2LL*a[i]+1)%Mod*inv6%Mod)%Mod+Mod)%Mod;
		Mu[i].a[2][1]=(a[i]+1);
		Mu[i].a[2][2]=Mu[i].a[1][1];
		Mu[i+n].a[1][1]=(1LL*a[i]*a[i]%Mod-Mu[i].a[1][1]+Mod)%Mod;
		Mu[i+n].a[1][2]=Mu[i].a[1][2];
		Mu[i+n].a[2][1]=a[i];
		Mu[i+n].a[2][2]=Mu[i].a[1][1];
		Beg[i].a[1][1]=1LL*(a[i])*(a[i]+1)%Mod*inv2%Mod;
		Beg[i].a[1][2]=Mu[i].a[1][2];
	}
	Beg[n+1]=Beg[1];
	PreMu[n]=Mu[n];
	Dwn(i,n-1,1)PreMu[i]=Mu[i]*PreMu[i+1];
	PreMu[n+1]=Mu[n+1];
	Rep(i,2,n)PreMu[i+n]=PreMu[i+n-1]*Mu[i+n];
	
	Rep(s,1,n){
		int i=s+1;
		Matrix Ans;Ans.Init();
		if(i==n+1){
			Rep(j,1,n-1)Ans=Ans*Mu[j+n];
			ans=(ans+1LL*Ans.a[1][1]*a[s]%Mod+Ans.a[1][2])%Mod;
			continue;
		}
		Ans=Ans*PreMu[i];
		//Ans=Ans*PreMu[s+n-1];
		if(s>1)Ans=Ans*PreMu[s+n-1];
		ans=(ans+1LL*Ans.a[1][1]*a[s]%Mod+Ans.a[1][2])%Mod;
//		cerr<<ans<<"\n";
	}

/*	Rep(s,1,n){
		memset(f,0,sizeof(f));
		int i=s+1;
		Rep(j,0,a[i])f[i][j]=a[i]-j;
		++i;
		for(;i<=n;++i)Rep(j,0,a[i])Rep(k,0,a[i-1])f[i][j]=(f[i][j]+1LL*(a[i]+k-j)*f[i-1][k])%Mod;
		if(i<=n)i=n+1;
		for(;i<s+n;++i){
			Rep(j,1,a[i])Rep(k,0,a[i-1])f[i][j]=(f[i][j]+1LL*(a[i]+k-j)*f[i-1][k])%Mod;
		}
		i=s+n-1;
		Rep(j,0,a[i])ans=(ans+1LL*f[i][j]*(a[s]+j)%Mod)%Mod;
	}
*/
	cout<<ans<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }


T4 O

不太会,题解解密不出来,数据很水我把错误做法卡了,但是卡之前我忘了先把它切掉,于是我现在也切不掉了。

9.12CSP-S短赛3(开小灶3)

T1 世界冰球锦标赛

原题,见开小灶2

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e2+10;

int n;
ll m;
ll v[maxn];
ll a[1<<20|1],b[1<<20|1];
int na,nb,Va,Vb;

#define lowbit(x) (x&-x)

void solve(){
	cin>>n>>m;Rep(i,1,n)cin>>v[i];
	na=n>>1,nb=n-na;
	Va=1<<na,Vb=1<<nb;
	Rep(i,1,na)a[1<<(i-1)]=v[i];
	Rep(i,1,nb)b[1<<(i-1)]=v[i+na];
	for(int i=1;i<Va;++i)a[i]=a[i^lowbit(i)]+a[lowbit(i)];
	for(int i=1;i<Vb;++i)b[i]=b[i^lowbit(i)]+b[lowbit(i)];
	sort(a+1,a+Va+1),sort(b+1,b+Vb+1);
	ll ans=0;
	int pr=upper_bound(b+1,b+Vb+1,m)-b-1;ans+=pr;
	Rep(i,2,Va){
		while(pr>=1 && a[i]+b[pr]>m)pr--;
		ans+=pr;
	}
	cout<<ans<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T2 滚

做法是比较显然的,维护当前的f值集合就行,最多只有根号个项,所以查询直接扫一遍就是对的

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=2e5+10,INF=1e9+20051107;

int n,m;
int a[maxn],w[maxn];
int cnt[maxn],vis[maxn];
int ANS[maxn];

int bel[maxn],L[maxn],R[maxn];
int siz,blen;
struct QQ{
	int l,r,k,id;
	bool operator<(const QQ &rhs)const{
		if(bel[l]==bel[rhs.l])return r<rhs.r;
		return l<rhs.l;
	}
}Q[maxn];
struct Ass{int l,r;}ass;

void Init(){
	siz=sqrt(n);blen=ceil(1.0*n/siz);
	Rep(i,1,blen){
		L[i]=R[i-1]+1,R[i]=i*siz;
		Rep(j,L[i],R[i])bel[j]=i;
	}
	R[blen]=n;
}

set <int> S;

void Add(int x){
	--vis[cnt[x]];if(vis[cnt[x]]==0)S.erase(cnt[x]);
	++cnt[x];++vis[cnt[x]];if(vis[cnt[x]]==1)S.insert(cnt[x]);
}

void Del(int x){
	--vis[cnt[x]];if(vis[cnt[x]]==0)S.erase(cnt[x]);
	--cnt[x];++vis[cnt[x]];if(vis[cnt[x]]==1)S.insert(cnt[x]);
}

int Query(int k){
	int ans=-1,last=-INF;
	for(auto it : S){
		if(last<it && it <= last + k)ans=max(ans,w[it]);
		last=it;
	}
	return ans;
}

void Work2(){
	Init();
	Rep(i,1,m)cin>>Q[i].l>>Q[i].r>>Q[i].k,Q[i].id=i;
	sort(Q+1,Q+m+1);
	ass.l=1,ass.r=0;
	Rep(i,1,m){
		while(ass.r<Q[i].r)Add(a[++ass.r]);
		while(ass.l>Q[i].l)Add(a[--ass.l]);
		while(ass.l<Q[i].l)Del(a[ass.l++]);
		while(ass.r>Q[i].r)Del(a[ass.r--]);
		ANS[Q[i].id]=Query(Q[i].k);
	}
	Rep(i,1,m)cout<<ANS[i]<<"\n";
}

void solve(){
	cin>>n>>m;
	Rep(i,1,n)cin>>a[i];Rep(i,1,n)cin>>w[i];
//	if(1LL*n*m<=1000000LL)return Work1();
	return Work2();
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

CSP-S模拟4

T1 石子游戏

待我透彻了再写

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=5e5+10;

int n;
int a[maxn],c[maxn];
int f[maxn][21];

void solve(){
	fre(stone);
	cin>>n;Rep(i,1,n)cin>>a[i],c[a[i]]^=1;
	Rep(i,1,n)c[i]+=c[i-1];
	Dwn(i,n,0){
		int lg=__lg(n-i+1);
		for(int j=0;j<=lg;++j) f[i][j]=f[min(i+(1<<(j+1)),n)][j]+c[min(i+(1<<(j+1))-1,n)]-c[i+(1<<j)-1];
	}
	Rep(x,2,n+1){
		int lim=__lg(x-1),SG=0;
		Rep(j,0,lim){
			for(int k=0;k*x<=n;++k){
				int l=k*x,r=min((k+1)*x-1,n);
				int floor=(r-l+1)>>(j+1);
				bool low=(l+(floor<<(j+1))+(1<<j))<=r;
				if((f[l][j]-f[l+(floor<<(j+1))][j]+low*(c[r]-c[l+(floor<<(j+1))+(1<<j)-1]))&1)SG^=1<<j;
			}
			if(SG)break;
		}
		if(SG)cout<<"Alice ";
		else cout<<"Bob ";
	}
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T2 大鱼吃小鱼

贪心吃当且能吃的最大的显然是对的,假设一条一条的吃,吃到某个时候,我们能吃更大的了,这时候我们一定会去吃这个更大的,于是我们可以考虑模拟这个过程。暴力的话就是拿个map维护所有鱼,然后按从小到大枚举,同时维护一个当且能吃的鱼的集合,我们每次考虑从能吃的里吃掉一些,使得我们能够扩大能吃的鱼的集合,或者先一步达到要求。暴力就是不断选最大的吃,但是显然每次我们吃的都是一段后缀,于是我们可以二分找到这一段一次性吃掉,然后我们就可以吃更大的鱼了。考虑当前体积为now,比now大的最小的鱼体积为x,我们可以一只log的复杂度达到大于x的体积,这时候我们可以吃x了,由于x>now,于是我们的体积翻倍,显然最多只会吃log次,复杂度两只log。

关于实现的话,线段树离散化之类的很难写,考虑用FHQ_Treap,每次把要吃掉的Split出来,剩下的合并然后递归处理,回溯的时候在按原来Split的界Split开,把吃掉的merge回去,就能复原了。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=5e5+10;

struct FHQ_Treap{
	#define lch V[p].l
	#define rch V[p].r
	struct Vertex{int l,r,key,val,siz,sum;}V[maxn];
	int tot,root;
	int New(int x){++tot;V[tot].val=V[tot].sum=x;V[tot].key=rand();V[tot].siz=1;return tot;}
	void Pushup(int p){V[p].siz=V[lch].siz+V[rch].siz+1;V[p].sum=V[lch].sum+V[rch].sum+V[p].val;}
	void Split_val(int p,int &rtx,int &rty,int x){
		if(p==0)return rtx=rty=0,void();
		if(x<V[p].val){Split_val(V[p].l,rtx,V[p].l,x),rty=p,Pushup(rty);return;}
		else {Split_val(V[p].r,V[p].r,rty,x),rtx=p,Pushup(rtx);return;}
	}
	void Split_siz(int p,int &rtx,int &rty,int x){
		if(p==0)return rtx=rty=0,void();
		if(V[lch].siz>=x){Split_siz(V[p].l,rtx,V[p].l,x),rty=p,Pushup(rty);return;}
		else {Split_siz(V[p].r,V[p].r,rty,x-V[lch].siz-1),rtx=p,Pushup(rtx);return;}
	}
/*	void Split_sum(int p,int &rtx,int &rty,int x){
		if(p==0)return rtx=rty=0,void();
		if(V[rch].sum>=x){ Split_sum(V[p].r,V[p].r,rty,x),rtx=p,Pushup(rtx);return; }
		if(V[rch].sum+V[p].val>=x){  }
	}
*/
	int Merge(int rtx,int rty){
		if(!rtx || !rty )return rtx|rty;
		if(V[rtx].key<V[rty].key){V[rtx].r=Merge(V[rtx].r,rty),Pushup(rtx);return rtx;}
		else {V[rty].l=Merge(rtx,V[rty].l),Pushup(rty);return rty;}
	}
	void Insert(int x){
		int l=0,r=0;Split_val(root,l,r,x);
		root=Merge(Merge(l,New(x)),r);
	}
	void Erase(int x){
		int l=0,r=0,m=0;Split_val(root,l,r,x);Split_val(l,l,m,x-1);
		m=Merge(V[m].l,V[m].r);
		root=Merge(Merge(l,m),r);
	}
	int Kth(int p,int x){
		if(!p)return 0;
		if(x<=0)return 0;
		if(V[rch].sum>=x)return Kth(V[p].r,x);
		if(V[p].val+V[rch].sum>=x)return V[rch].siz+1;
		else return Kth(V[p].l,x-V[rch].sum-V[p].val)+V[rch].siz+1;
	}
	int Min(int p){ while(V[p].l)p=V[p].l; return V[p].val; }
	int Query(int now,int k){
		if(now>=k)return 0;
		if(V[root].sum<k-now)return -1;
		int l=0,m=0,r=0;Split_val(root,l,r,now-1);
		int nxt=Min(r);if(!nxt)nxt=k;int goal=min(k-now,nxt+1-now);
		if(V[l].sum<goal){ root=Merge(l,r);return -1; }
		Split_siz(l,l,m,V[l].siz-Kth(l,goal));
		root=Merge(l,r);int tmp=V[m].siz;

		int res=Query(now+V[m].sum,k);

		l=0,r=0;Split_val(root,l,r,now-1);
		root=Merge(Merge(l,m),r);
		if(res==-1)return -1;
		else return tmp+res;
	}
}T;

int n,m;

void solve(){
	fre(fish);
	cin>>n;int x;
	Rep(i,1,n)cin>>x,T.Insert(x);
	cin>>m;int opt,K;
	while(m--){
		cin>>opt;
		if(opt==1){
			cin>>x>>K;
			cout<<T.Query(x,K)<<endl;
		//	cerr<<"All :: "<<T.V[T.root].sum<<endl;
			continue;
		}
		cin>>x;
		if(opt==2)T.Insert(x);
		if(opt==3)T.Erase(x);
	}
}

#undef int
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }


T3 黑客

比较裸,大概是普及题。枚举一下分子分母,然后算一下gcd的取值范围就行

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=1e5+10,Mod=1e9+7;

int A,B,C,D;
int ans;

void solve(){
	fre(hacker);
	cin>>A>>B>>C>>D;
	/*Rep(i,A,B)Rep(j,C,D){
		int g=__gcd(i,j);int sum=i/g+j/g;
		if(sum<=999)ans+=sum;
	}*/
	Rep(i,1,999)Rep(j,1,999)if(i+j<=999 && (__gcd(i,j)==1)){
		int la=((A-1)/i+1),ra=(B/i);
		int lb=((C-1)/j+1),rb=(D/j);
		int l=max(la,lb),r=min(ra,rb);
		if(l<=r)ans=(ans+1LL*(r-l+1)*(i+j));
		
	}
	cout<<ans%Mod<<"\n";
}

#undef int 
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T4 黑客2

原题,题解见 暑假集训 -8.3多校联训6

下边这份代码总用时可以跑进900ms以内
image

但是如果你考虑反向思考,求出不合法的部分,由于出题人造的数据多数点的限制都很少,限制多的长度又很小。所以可以算不合法的然后用总的减掉大概,我没有实现,理论上会快不少,如果你的常数不好卡的话可以试试这样写,所有方案的总和甚至是可以打表的,多实现个高精减就行了。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define ll ull
using namespace std;

const int maxn=5e2+10,M=540;

struct BigInt{
	static const ll base=1e18;
	ll a[60];int len;
	BigInt(){memset(a,0,sizeof(a));len=0;}
	void Init(){Rep(i,0,len)a[i]=0;len=0;}
	ll &operator[](const int &i){return a[i];}
	ll operator[](const int &i)const{return a[i];}
	void operator += (const BigInt &x){
		int Lim=max(x.len,len);
		Rep(i,0,Lim){
			a[i]+=x[i];
			a[i+1]+=(a[i]/base);
			a[i]%=base;
		}
		len=Lim; while(a[len]!=0){++len;}--len; len=max(len,0);
	}
	friend BigInt operator * (const BigInt &x,const int k){
		BigInt res;
		Rep(i,0,x.len){
			res[i]+=x[i]*k;
			res[i+1]+=res[i]/base;
			res[i]%=base;
		}
		res.len=x.len; while(res[res.len]!=0){++res.len;}--res.len; res.len=max(res.len,0);
		return res;
	}
	void Print(){ cout<<a[len];Dwn(i,len-1,0){cout<<setw(18)<<setfill('0')<<a[i]; } }
};


BigInt f[2][M],sum[2][M],tmp1,tmp2;
int n,m,K,V;
BigInt ans,ass;
int nop[20],mus[20];
void solve(){
	fre(hacker2);
	cin>>n>>m>>K;int x,y;
	V=(1<<(K))-1;
	Rep(i,1,m){ cin>>x>>y; nop[x]|=(1<<(y-1)),mus[y]|=(1<<(x-1)); }
	int opt=1;
	Rep(i,1,K)f[opt][1<<(i-1)][0]=1,sum[opt][1<<(i-1)][0]=i;
	Dwn(i,n,2){
		Rep(j,1,V){
			if(f[opt][j].a[0]==0 && f[opt][j].len==0)continue;
				tmp1=(sum[opt][j]*10);tmp2=f[opt][j];
				Rep(l,1,K){
					if((mus[l]&j)!=0){tmp2+=f[opt][j];continue;}
					f[opt^1][j|(1<<(l-1))]+=f[opt][j];
					sum[opt^1][j|(1<<(l-1))]+=tmp1;
					sum[opt^1][j|(1<<(l-1))]+=tmp2;
					tmp2+=f[opt][j];
				}
			f[opt][j].Init(),sum[opt][j].Init();
		}
		opt^=1;
	}
	Rep(i,1,V) ans+=f[opt][i],ass+=sum[opt][i];
	ans.Print();//cerr<<"\n";cerr<<ans.len<<"\n";
	cout<<"\n";
	ass.Print();//cerr<<"\n";cerr<<ass.len<<"\n";
	cout<<endl;
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }


## 9.7A层2

T1 Coprime Array

对于一个上界k的计算是比较显然的,枚举gcd,然后计算出每个位置可以选的数,实际上是从一开始的一段区间。考场上暴力做是直接枚举倍数容斥的,但实际并不用。发现我们选定了一个gcd后,实际上是变成了一个规模更小的问题,于是我们直接利用之前算出来的答案即可。观察可以发现,k每增一,只会有log个k的约数的位置会发生变化,于是直接预处理约数然后修正变化量即可

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=2e6+10,Mod=1e9+7;

int pw(int x,int p){int res=1,base=x;while(p){if(p&1)res=1LL*res*base%Mod;base=1LL*base*base%Mod;p>>=1;}return res;}
int Inv(int x){return pw(x,Mod-2);}

int n,K;
int f[maxn],p[maxn],ans;
int res[maxn];
vector<int>vec[maxn];

void solve(){
	cin>>n>>K;
	Rep(i,1,K)res[i]=pw(i,n);
	Rep(i,2,K)for(int j=1;i*j<=K;++j)vec[i*j].push_back(i);
	int sum=0;
	Rep(i,2,K){
		for(auto it : vec[i])sum=(0LL+sum-res[(i-1)/it]+res[i/it]+Mod)%Mod;
		res[i]=(res[i]-sum+Mod)%Mod;
	}
	Rep(i,1,K)ans=(ans+(res[i]^i))%Mod;
	cout<<ans<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }


T2 Werewolves

是比较正常的树上背包,做的很少于是并不会,转化也很好,枚举颜色求,当前颜色的值设为1,其他为-1,就是要求和大于0的联通块个数,做树上背包即可。

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=3e3+10,Mod=998244353;

int ans,n;
int f1[maxn][maxn],f2[maxn][maxn],f3[maxn];
int tmp1[maxn][maxn],tmp2[maxn][maxn],tmp3[maxn];

struct Graph{
	struct eg{int from,to,next;}e[maxn*2];
	int len,head[maxn];int col[maxn],w[maxn],siz[maxn];
	int fa[maxn];vector<int>son[maxn];int sum;
	void lqx(int from,int to)
	{ e[++len].from=from,e[len].to=to,e[len].next=head[from],head[from]=len; }
	void Predfs(int u){
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;if(v==fa[u])continue;
			fa[v]=u;Predfs(v);son[u].push_back(v);
		}
	}
	void Col(int c){ 
		sum=0;Rep(u,1,n)w[u]=(col[u]==c ? 1 : -1),sum+=(col[u]==c); 
		Rep(u,1,n)Rep(i,0,sum)f1[u][i]=f2[u][i]=f3[u]=0;
	}
	void Dfs(int u){
		siz[u]=1;if(w[u]==1)f1[u][1]=1;else f2[u][1]=1;
		for(auto v : son[u]){
			Dfs(v);
			int lim=min(siz[u],sum);
			Rep(i,0,lim)tmp1[u][i]=f1[u][i],tmp2[u][i]=f2[u][i];
			tmp3[u]=f3[u];
			f3[u]=(f3[u]+1LL*tmp3[u]*f3[v])%Mod;
			Rep(i,1,min(siz[v],sum))
				f1[u][i]=(f1[u][i]+1LL*tmp3[u]*f1[v][i])%Mod,
				f2[u][i]=(f2[u][i]+1LL*tmp3[u]*f2[v][i])%Mod;
			Rep(i,1,lim){
				f1[u][i]=(f1[u][i]+1LL*tmp1[u][i]*f3[v])%Mod;
				f2[u][i]=(f2[u][i]+1LL*tmp2[u][i]*f3[v])%Mod;
				for(int j=1;j<=siz[v] &&j <=sum;++j){
					if(i+j<=sum)
						f1[u][i+j]=(f1[u][i+j]+1LL*tmp1[u][i]*f1[v][j])%Mod,
						f2[u][i+j]=(f2[u][i+j]+1LL*tmp2[u][i]*f2[v][j])%Mod;
					if(i>j)
						f1[u][i-j]=(f1[u][i-j]+1LL*tmp1[u][i]*f2[v][j])%Mod,
						f2[u][i-j]=(f2[u][i-j]+1LL*tmp2[u][i]*f1[v][j])%Mod;
					if(i<j)
						f1[u][j-i]=(f1[u][j-i]+1LL*tmp2[u][i]*f1[v][j])%Mod,
						f2[u][j-i]=(f2[u][j-i]+1LL*tmp1[u][i]*f2[v][j])%Mod;
					if(i==j)f3[u]=(0LL+f3[u]+1LL*tmp1[u][i]*f2[v][j]%Mod+1LL*tmp2[u][i]*f1[v][j]%Mod)%Mod;
				}
			}
			siz[u]+=siz[v];
		}
		Rep(i,1,min(siz[u],sum))ans=(ans+f1[u][i])%Mod;
	}
}G;

void solve(){
	cin>>n;Rep(i,1,n)cin>>G.col[i];
	int x,y;Rep(i,2,n)cin>>x>>y,G.lqx(x,y),G.lqx(y,x);
	G.Predfs(1);
	Rep(i,1,n){ G.Col(i);G.Dfs(1);}
	cout<<ans<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

CSP-S模拟3(A层)

T1 数据恢复

赛时只会假贪心,如果说是在一个无限制的序列上做的话就是对的,现在要考虑的就是把这种做法上树。仍然用一个堆把所有点塞进去维护,如果堆顶没有依赖限制了,那么显然是一定要取它的,如果还有限制,那么就把他和它父亲绑成一个点,表示如果取了他父亲,那么一定会马上取它,这样做一遍就行。

点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=3e5+10;

int n;
int a[maxn],b[maxn];
ll ans,sum;
int fa[maxn*30];

struct Ver{
	ll a,b,id,tim;
	bool operator < (const Ver &rhs)const{
		if(b!=rhs.b)return b*rhs.a<rhs.b*a;
		else return a>rhs.a;
	}
}V[maxn*30];
priority_queue<Ver>q;

int f[maxn*30];
int Find(int x){return f[x]==x ? x : f[x]=Find(f[x]);}
int Te;int vis[maxn*30],del[maxn*30];
ll sumb;


void solve(){
	fre(data);
	cin>>n;int x;
	Rep(i,2,n)cin>>x,fa[i]=x;
	Rep(i,1,n)cin>>a[i]>>b[i],V[i]=Ver{a[i],b[i],i,i};
	Rep(i,1,n)f[i]=i;
	Rep(i,2,n)q.push(Ver{a[i],b[i],i,i});
	int cnt=n;vis[1]=1;
	Te=n;sumb=b[1];
	while(!q.empty()){
		++Te;int y=q.top().tim;x=q.top().id;
		if(del[y]){q.pop();continue;}
		int fx=Find(fa[x]);
		if(vis[fx]){ vis[x]=1;--cnt;ans+=sumb*q.top().a;sumb+=q.top().b;q.pop(); }
		else{
			f[x]=fx;
			ans+=V[fx].b*q.top().a;
			del[V[fx].tim]=1;
			V[fx]=Ver{V[fx].a+q.top().a,V[fx].b+q.top().b,V[fx].id,Te};
			q.pop();q.push(V[fx]);
			--cnt;
		}
	}
	cout<<ans<<"\n";
}

#undef int 
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T2 下落的小球

比较水的一眼题,考虑从上往下,依次把当前节点接收到的小球分配给儿子,然后再从儿子一层一层算回来,合并儿子时,设当前节点u接收到now个小球,每个儿子被其分配了pre[v]个节点,u这棵子树一共有多少次取球机会为siz,那么我们需要让每个儿子的前pre次出现的这siz的前部,而他们内部没有顺序要求,然后让剩下的次数出现在siz次的后部,内部也没有顺序要求,分别算一下两部分分配的方案数,再乘上每个儿子子树的方案数就能完成转移,注意要用真实的取球机会算而不是球的个数,因为可能会有不合法的测试点,这时候组合数会越界,高端的O2会贴心的帮你置为0。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e6+10,Mod=1e9+7;

int pw(int x,int p){int res=1,base=x;while(p){if(p&1)res=1LL*res*base%Mod;base=1LL*base*base%Mod;p>>=1;}return res;}
int Inv(int x){return pw(x,Mod-2);}
int n;
int fac[maxn],inv[maxn];

void Init(const int N=1000000){
	fac[0]=inv[0]=1;
	Rep(i,1,N)fac[i]=1LL*fac[i-1]*i%Mod;
	inv[N]=Inv(fac[N]);
	Dwn(i,N-1,1)inv[i]=1LL*inv[i+1]*(i+1)%Mod;
}
int C(int n,int m){ return 1LL*fac[n]*inv[m]%Mod*inv[n-m]%Mod; }

struct Graph{
	vector<int>son[maxn];int fa[maxn];
	int val[maxn],sum[maxn],siz[maxn],now[maxn];
	int pre[maxn],f[maxn];
	void PreDfs(int u){
		sum[u]=val[u],siz[u]=1,now[u]=1;
		for(auto v : son[u]){PreDfs(v);sum[u]+=sum[v];siz[u]+=siz[v];}
	}
	void Dfs(int u){
		f[u]=1;
		if(son[u].empty())return;
		for(auto v : son[u]){
			pre[v]=sum[v]-siz[v];
			now[v]+=pre[v];
			Dfs(v);
			f[u]=1LL*f[u]*f[v]%Mod;
		}
		int all = now[u];
		for(auto v : son[u]){ f[u]=1LL*f[u]*C(all,pre[v])%Mod; all-=pre[v]; }
		all = sum[u] - now[u];
		for(auto v : son[u]){ f[u]=1LL*f[u]*C(all,(sum[v]-pre[v]))%Mod;all-=(sum[v]-pre[v]); }
	}
}G;

void solve(){
	fre(ball);
	cin>>n;Init();
	Rep(i,2,n)cin>>G.fa[i],G.son[G.fa[i]].push_back(i);
	Rep(i,1,n)cin>>G.val[i];
	G.PreDfs(1);G.Dfs(1);
	cout<<G.f[1]<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T3 消失的运算符

考虑把所有数字两侧都加上括号,那么现在每一个括号就是一个计算单元,我们逐个计算单元逐层计算,跑树上背包,合并时枚举用加号还是乘号连接,处理一下合并后的后缀积来适应不同的转移

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=3e5+10,M=5e3+10,Mod=1e9+7;

int n,K,m;
char s[maxn],str[maxn];
int st[maxn],top;
int match[maxn];
int C[M][M];
int tot;
int f[M][M],siz[maxn];

bool isnum(const char &c){return (c>='0' && c<='9');}

void Init(){
	int len=n;n=0;
	Rep(i,1,len){
		if(isnum(s[i])){ str[++n]='(',str[++n]=s[i],str[++n]=')'; }
		else{
			str[++n]=s[i];m+=(s[i]=='-');
		}
	}
	Rep(i,1,n){
		if(str[i]=='(')st[++top]=i;
		else if(str[i]==')'){match[st[top]]=i,match[i]=st[top];--top;}
	}
	C[0][0]=1;
	Rep(i,1,m){ C[i][0]=1; Rep(j,1,i)C[i][j]=(C[i-1][j]+C[i-1][j-1])%Mod; }
}

int tmp[M+5];

int Sol(int l,int r){
	if(match[l]==r)return Sol(l+1,r-1);
	int u=++tot,*fu=f[u];
	if(l==r)return fu[0]=(str[l]-'0'),u;
	int g[M+5];
	for(int p=l,v,fir=true;p<r;){
		if(str[p]!='(')	{++p;continue;}
		const int *fv=f[v=Sol(p+1,match[p]-1)];p=match[p]+1;
		if(fir){
			Rep(i,0,siz[v])fu[i]=g[i]=fv[i];
			siz[u]=siz[v];fir=false;
			continue;
		}
	
		Rep(i,0,siz[u])Rep(j,0,siz[v]){
			// + 
			tmp[i+j+1]=(0LL+tmp[i+j+1]+1LL*fu[i]*C[siz[v]][j]%Mod+1LL*fv[j]*C[siz[u]][i]%Mod)%Mod;
	
			// *
			tmp[i+j]=(0LL+tmp[i+j]+1LL*(fu[i]-g[i])*C[siz[v]][j]%Mod+1LL*g[i]*fv[j]%Mod)%Mod;
		}
		Rep(i,0,siz[u]+siz[v]+1)fu[i]=tmp[i],tmp[i]=0;
		Rep(i,0,siz[u])Rep(j,0,siz[v]){
			tmp[i+j]=(tmp[i+j]+1LL*g[i]*fv[j])%Mod;
			tmp[i+j+1]=(tmp[i+j+1]+1LL*C[siz[u]][i]*fv[j])%Mod;
		}
		Rep(i,0,siz[u]+siz[v]+1)g[i]=tmp[i],tmp[i]=0;
		siz[u]+=siz[v]+1;
	}
	return u;
}

void solve(){
	fre(operator);
	cin>>n>>K>>(s+1);
	Init();
	cout<<(f[Sol(1,n)][K]+Mod)%Mod<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T4 古老的序列问题

奇妙的分治做法,考虑像CDQ一样,处理过mid的询问,左边对右边的贡献,对于一个左端点在\([l,mid]\),右端点在\([mid+1,r]\)的区间,可能是minmax同时在左边取,minmax其一在右边取,minmax同时在右边取,分四种情况用四棵线段树维护每个在\([mid+1,r]\)的右边界的当前贡献,不断移动左界,更新其对右端点的贡献即可。然后把询问拆成只在左或右一侧的继续递归下去,注意完全覆盖当前处理的\([l,r]\)的询问需要特殊处理,不再下放,否则空间可能会被卡成\(n^2\)

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=4e5+10,INF=1e9+20051107,Mod=1e9+7;

int n,m,s[maxn];
struct Ver{
	int l,r,id;
	bool operator<(const Ver &rhs)const{return l>rhs.l;}
};
vector<Ver>q[maxn];

struct Seg{
	int L,R;
	struct Tree{int sum,sx,lazy;}tr[maxn];
	void Update(int rt,int w){
		tr[rt].sum=(tr[rt].sum+1LL*tr[rt].sx*w)%Mod;
		tr[rt].lazy=(tr[rt].lazy+w)%Mod;
	}
	void Pushup(int rt){tr[rt].sum=(tr[rt<<1].sum+tr[rt<<1|1].sum)%Mod;}
	void Build(int rt,int l,int r,int *a){
		if(rt==1)L=l,R=r;
		tr[rt].sum=tr[rt].lazy=0;
		if(l==r)return tr[rt].sx=a[l],void();
		int mid=(l+r)>>1;
		Build(rt<<1,l,mid,a),Build(rt<<1|1,mid+1,r,a);
		tr[rt].sx=(tr[rt<<1].sx+tr[rt<<1|1].sx)%Mod;
	}
	void Pushdown(int rt){ if(!tr[rt].lazy)return; Update(rt<<1,tr[rt].lazy),Update(rt<<1|1,tr[rt].lazy);tr[rt].lazy=0; }
	void Modify(int rt,int l,int r,int s,int t,int w){
		if(s>r || t<l)return;
		if(s<=l && t>=r)return Update(rt,w);
		int mid=(l+r)>>1;
		Pushdown(rt);
		if(s<=mid)Modify(rt<<1,l,mid,s,t,w);
		if(t>mid)Modify(rt<<1|1,mid+1,r,s,t,w);
		Pushup(rt);
	}
	int Query(int rt,int l,int r,int s,int t){
		if(s>r || t<l)return 0;
		if(s<=l && t>=r)return tr[rt].sum;
		int mid=(l+r)>>1,res=0;
		Pushdown(rt);
		if(s<=mid)res=Query(rt<<1,l,mid,s,t);
		if(t>mid)res=(res+Query(rt<<1|1,mid+1,r,s,t))%Mod;
		return res;
	}
	int Get(int x){return Query(1,L,R,L,x);}
}I,A,B,AB;

int f[maxn],ans[maxn];
int a[maxn],b[maxn],z[maxn],ab[maxn];

void Sol(int p,int l,int r){
	if(l==r){
		f[p]=1LL*s[l]*s[l]%Mod;
		for(auto it : q[p])ans[it.id]=(ans[it.id]+f[p])%Mod;
		return;
	}
	int mid=(l+r)>>1;a[mid]=Mod,b[mid]=0;
	vector<Ver>v;
	for(auto it : q[p])if(it.l<=mid && it.r>mid && !(it.l==l && it.r==r))v.push_back(it);
	sort(v.begin(),v.end());
	Rep(i,mid+1,r){
		z[i]=1;
		a[i]=min(a[i-1],s[i]),b[i]=max(b[i-1],s[i]);
		ab[i]=1LL*a[i]*b[i]%Mod;
	}
	I.Build(1,mid+1,r,z);A.Build(1,mid+1,r,a);
	B.Build(1,mid+1,r,b);AB.Build(1,mid+1,r,ab);
	int pa=mid,pb=mid,la=Mod,lb=0;
	for(int i=mid,j=0;i>=l;--i){
		la=min(la,s[i]),lb=max(lb,s[i]);
		for(;pa<r && a[pa+1]>la;++pa);
		for(;pb<r && b[pb+1]<lb;++pb);
		I.Modify(1,mid+1,r,mid+1,min(pa,pb),1LL*la*lb%Mod);
		if(pa<pb)A.Modify(1,mid+1,r,pa+1,pb,lb);
		if(pb<pa)B.Modify(1,mid+1,r,pb+1,pa,la);
		AB.Modify(1,mid+1,r,max(pa,pb)+1,r,1);
		while(j<v.size() && v[j].l>=i){
			Ver it=v[j];
			ans[it.id]=(0LL+ans[it.id]+I.Get(it.r)+A.Get(it.r)+B.Get(it.r)+AB.Get(it.r))%Mod;
			++j;
		}
	}
	f[p]=(0LL+I.Get(r)+A.Get(r)+B.Get(r)+AB.Get(r))%Mod;
	for(auto it : q[p]){
		if(it.l==l && it.r==r)continue;
		if(it.r<=mid)q[p<<1].push_back(it);
		else if(it.l>mid)q[p<<1|1].push_back(it);
		else q[p<<1].push_back(Ver{it.l,mid,it.id}),q[p<<1|1].push_back(Ver{mid+1,it.r,it.id});
	}
	Sol(p<<1,l,mid),Sol(p<<1|1,mid+1,r);
	f[p]=(0LL+ f[p] +f[p<<1] + f[p<<1|1])%Mod;
	for(auto it : q[p])if(it.l==l && it.r==r)ans[it.id]=(ans[it.id]+f[p])%Mod;
}

void solve(){
	fre(sequence);
	cin>>n>>m;
	Rep(i,1,n)cin>>s[i];
	Rep(i,1,m){ int x,y;cin>>x>>y; q[1].push_back(Ver{x,y,i}); }
	Sol(1,1,n);
	Rep(i,1,m)cout<<ans[i]<<"\n";
}

#undef int
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

51nod模拟4/CSP-S模拟3

T1 score and rank

%%t%ourist

比较智慧的贪心。考虑类似求最大子段和类的东西,我们逐个i考虑,当$a_i <0 $时,以i结尾的最大子段显然不会再超过S,但是它会抵消掉一部分之前的数

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define pli pair<long long ,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e6+10,INF=1e9+7;
const ll LINF=1e15;

int a[maxn];
ll S,pre[maxn];
int n,ans;
multiset<ll>mS;
ll sum;

void solve(){
	cin>>n>>S;
	Rep(i,1,n)cin>>a[i],pre[i]=pre[i-1]+a[i];
	if(S<0){
		int res=0;
		Rep(i,1,n)res+=(a[i]>=S);
		return cout<<res<<"\n",void();
	}
	Rep(i,1,n){
		int x=a[i];
		if(x>0){
			mS.insert(x);sum+=x;
			while(sum>=S && (!mS.empty())){
				auto it = prev(mS.end());
				sum-=*it;
				mS.erase(it);
				++ans;
			}
		}
		if(x<0){
			if(sum+x>0){
				sum+=x;
				while(x<0 && (!mS.empty())){
					auto it = mS.begin();
					x+=*it;
					mS.erase(it);
				}
				mS.insert(x);
			}else mS.clear(),sum=0;
		}
	}
	cout<<ans<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T2 HZOI大作战

做法比较显然,发现每个询问都是从v到u做一次单调栈,于是想到可撤销栈,实际上就是倍增,处理出每个点向上走,交换了\(2^i\)次后所在的点,询问就是二分找到第一个大于初始值的位置,然后再实现一个查询从u到v的交换次数即可。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;


#define gc if(++ip==ie)fread(ip=buf,1,SZ,stdin)
const int SZ=1<<21;char buf[SZ],*ie=buf+SZ,*ip=ie-1;
inline int read(){ gc;while(*ip<'-')gc; bool f=*ip=='-';if(f)gc; int x=*ip&15;gc; while(*ip>'-'){x*=10;x+=*ip&15;gc;} return f ? -x : x; }

const int maxn=5e5+10,maxm=1e6+10,INF=1e9;

int fa[maxn][19],n,ans[maxn],f[maxn],q;

struct Graph{
	struct eg{int from,to,next;}e[maxm];
	int len,head[maxn];int dep[maxn],w[maxn];
	void lqx(int from,int to)
	{ e[++len].from=from,e[len].to=to,e[len].next=head[from],head[from]=len; }
	void Predfs(int u){
		dep[u]=dep[f[u]]+1;
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;if(v==f[u])continue;
			f[v]=u;Predfs(v);
		}
	}
	void Dfs(int u){
		int x=f[u];
		Dwn(i,18,0){
			if(w[u]>=w[fa[x][i]]){ x=fa[fa[x][i]][0]; }
		}
		if(w[u]>=w[x])x=fa[x][0];
		fa[u][0]=x;
		for(int j=1;j<=18;++j)fa[u][j]=fa[fa[u][j-1]][j-1];
		for(int i=head[u];i;i=e[i].next)if(e[i].to!=f[u])Dfs(e[i].to);
	}
	int Calc(int u,int v){
		int ans=0;
		Dwn(i,18,0){
			if(dep[fa[u][i]]>=dep[v]){
				u=fa[u][i];ans+=(1<<i);
			}
		}
		return ans;
	}
	void Sol(int u,int v,int val){
		int ans=0;
		if(val<w[u])return cout<<Calc(u,v)+1<<"\n",void();
		Dwn(i,18,0){
			if(dep[fa[u][i]]>=dep[v] && w[fa[u][i]]<=val){
				u=fa[u][i];
			}
		}
		u=fa[u][0];
		return cout<<Calc(u,v)+(dep[u]>=dep[v])<<"\n",void();
	}
}G;

void solve(){
	cin>>n>>q;Rep(i,1,n)cin>>G.w[i];G.w[0]=INF;
	int x,y,z;Rep(i,2,n)cin>>x>>y,G.lqx(x,y),G.lqx(y,x);
	G.Predfs(1);
	G.Dfs(1);
	while(q--){
		cin>>x>>y>>z;
		G.Sol(x,y,z);
	}
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T3 Delov的旅行

这题数据是我造的,于是在应验题组统一题面风格的要求下改了题面。
如果毒瘤数据给您带来了较差的游戏体验请来问候凉心造数据人
对数据的一些解释

  • 由于验题组赛时几乎没人写这题,于是只有一份贪心做法,我只能对着它卡,甚至我不知道如何构造卡贪心的数据。
  • 所有数据都是随机生成
  • 由于难以构造数据,为了卡掉可能出现的奇奇怪怪的做法,我只能稍加测试数据量并设置subtask保证不会有人乱搞拿到不该拿的分,为真正在努力做题的人提供良好的区分度
  • 对于大样例,所有大样例假贪心都能过我故意的,提醒大家不要太相信大样例
  • 每一个subtask都有卡贪心的点,甚至小数据都有
  • 由于数据随机,较小常数的正确做法应该是可以跑进两百毫秒以内的,为了提升大家的游戏体验,我没有调小时限来再卡掉一些并不太正确的做法,同时也避免时限造成的对正解复杂度分析的误导。当然,我简单构造了数据来卡掉某种错误做法。
  • 如果你通过某种乱搞拿到了二十分以上的分数,请来与我交流

下面是题解
题意比较清楚,就是每天都要到一个叶子,最后一天回到1,要让每天的通行费的最大值最小。

Subtask1(5pts)

显然最大的通行费一定是从1号点左子树的一个叶子节点到右子树的一个叶子节点时产生的,直接算就行。属于是送分点

Subtask2(15pts)

叶子节点数最大为8,于是直接全排列枚举访问顺序即可,由于每条边只能走两次的限制,所以每次走进一棵子树则必定把这棵子树的所有叶子都访问完再出去,需要考虑一下访问顺序是否合法。这个也是送分点

到此为大众分二十分。

Subtask3(20pts)

这一部分是给暴力dp的分数,如果想到的话已经接近正解了。
考虑二分答案转化为判定。
我们发现,对于一棵子树来说,它的每种访问方案的信息,只有第一次进入,和最后一次返回根的两条路径长度有用,因为中间两个叶子节点产生的费用如果合法那么就是合法了之后也不会用到,只有进和出会在后边用到,一种贪心的思想就是让入和出尽量小,但是这是一个二维的偏序,显然贪心是有问题的,于是我们把贪心做法卡掉了。
\(f[u][a][b]\)为以u为根的子树内,u到访问的第一个叶子节点的路径长度为a(即进子树),u到访问的最后一个叶子节点的路径长度为b(即出子树),且中间访问过程产生的最大费用不超过mid是否可行。转移是很显然的。考虑在一个节点是先进左子树还是先进右子树,把中间跨越子树时的那条路径Check一下是否大于mid即可,注意我们是不需要Check a和b是否合法的,因为第一天和最后一天不需要付钱。

在数据限制下,n最大为63,a,b最大为一百左右,我没试但是应该没啥问题,如果你写对了但没拿到的话请来问候凉心造数据人。

Subtask4(20pts)

这一部分是给大概\(n^2\)的做法的,但是我也不知道\(n^2\)能怎么做,也许你们有比较优秀的做法,原题里有这个部分分于是我就加上了。

Subtask5(40pts)

正解部分。发现在暴力dp时是有很多无用状态的,有大量根本不存在的状态,也有很多一定劣的状态,于是我们考虑每个点维护一个集合,存所有可能贡献的状态。集合里的状态一定是在a严格单增的同时b严格单减的。在合并两棵子树转移时,一个状态在另一棵子树上能匹配的状态一定是其集合的一个前缀,并且取a最大的那个一定最优,因为此时b最小。具有单调性于是可以双指针扫一遍转移。每个点的集合大小感性上看最劣都是\(O(n)\)的,但是这个上界远远达不到,甚至在随机数据下就是一个很小的常数,上界可能是log级别的,我们并不知道如何把状态数卡到上界,于是只能缩小时限,并且卡掉一些维护得不太严格的做法。

点击查看代码



#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".ans","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=(1<<17)+10;

int n;

struct Ver{
	ll a,b;
	bool operator<(const Ver &rhs)const{
		if(a!=rhs.a)return a<rhs.a;
		return b>rhs.b;
	}
};

set<Ver>S[maxn];

ll Lim;
bool flag;

struct Graph{
	struct eg{int from,to,next,dis;}e[maxn*2];
	int len,head[maxn];int w[maxn],fa[maxn],dfn[maxn];
	vector<pii>son[maxn];
	void lqx(int from,int to,int dis)
	{ e[++len].from=from,e[len].to=to,e[len].next=head[from],e[len].dis=dis,head[from]=len; }
	void Predfs(int u){
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;if(v==fa[u])continue;
			fa[v]=u;Predfs(v);son[u].push_back(mair(v,e[i].dis));
		}
	}
	Ver tmp[maxn],st[maxn];int tot,top;
	void Dfs(int u){
		if(!flag)return;
		S[u].clear();
		if(son[u].empty())return S[u].insert(Ver{0,0}),void();
		assert(son[u].size()==2);
		int x=son[u][0].fir,y=son[u][1].fir;
		Dfs(x),Dfs(y);tot=0;
		if(!flag)return;
		{
			auto itx = S[x].begin(),ity=S[y].begin(),Ed=prev(S[y].end());
			while(itx != S[x].end()){
				while(ity!=Ed && (*itx).b+son[u][0].sec+son[u][1].sec+(*next(ity)).a<=Lim)++ity;
				if((*itx).b+son[u][0].sec+(*ity).a+son[u][1].sec<=Lim)tmp[++tot]=Ver{(*itx).a+son[u][0].sec,(*ity).b+son[u][1].sec};
				++itx;
			}
		}
		{
			auto itx = S[x].begin(),ity=S[y].begin(),Ed=prev(S[x].end());
			while(ity != S[y].end()){
				while(itx!=Ed && (*next(itx)).a+son[u][0].sec+son[u][1].sec+(*ity).b<=Lim)++itx;
				if((*ity).b+son[u][1].sec+(*itx).a+son[u][0].sec<=Lim)tmp[++tot]=Ver{(*ity).a+son[u][1].sec,(*itx).b+son[u][0].sec};
				++ity;
			}
		}
		if(!tot)return flag=false,void();
		sort(tmp+1,tmp+tot+1);top=0;
		st[++top]=tmp[1];
		Rep(i,2,tot){
			if(tmp[i].a==st[top].a)st[top].b=min(st[top].b,tmp[i].b);
			else if(tmp[i].b<st[top].b)st[++top]=tmp[i];
		}
		Rep(i,1,top)S[u].insert(st[i]);
		if(S[u].empty())flag=false;
	}
}G;

void solve(){
	cin>>n;int x,y,w;
	Rep(i,2,n)cin>>x>>w,G.lqx(i,x,w),G.lqx(x,i,w);
	G.Predfs(1);
	ll l=-1,r=100000000000;
	while(r-l>1){
		ll mid=(l+r)>>1;
		Lim=mid,flag=true;G.Dfs(1);
		if(flag)r=mid;
		else l=mid;
	}
	cout<<r<<"\n";
}

#undef int
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T4 gtm和joke的星球

是斯坦纳树板子题,可能比较不友好,区分度很差,毕竟会就切了,不会也没法,当然最痛苦的是知道这是个板子但是不会打的。

如果你不知道这是个板子,那么你是更有可能拿高分甚至切掉的。在较弱的数据下,随机化做法是跑的非常优秀的(甚至在验题时我的随机化做法切掉了),应该会有不少人写随机化的。比较naive的想法就是建最小生成树然后把不用的边减掉,在树的部分分里这是绝对正确的,但是别的就不行了,于是我们考虑随机建出生成树,然后删无用边,在本题数据下可以拿到50分。数据不是我造的,如果我造的话可能会给随机化更多分,以提高一点区分度。

随机化


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e2+10,maxm=5e2+10;

int n,m,K;

mt19937 rd((unsigned long long ) &maxn);
int rr(int l, int r){
	uniform_int_distribution <> d(l, r);
	return d(rd);
}



struct UN{
	int f[maxn];
	void Init(int N){Rep(i,1,N)f[i]=i;}
	int Find(int x){return f[x]==x ? x : f[x]=Find(f[x]);}
	void Merge(int x,int y){
		x=Find(x),y=Find(y);
		f[y]=x;
	}
}S;

struct edg{
	int from,to,dis;
	bool operator<(const edg &rhs)const{ return dis<rhs.dis; }
}es[maxm];

int a[maxn];
int ans=1000000000;

struct Graph{
	struct eg{int from,to,dis,next;}e[maxm];
	int len,head[maxn];int dp[maxn],siz[maxn];
	void Clear(){
		Rep(i,1,n)dp[i]=siz[i]=head[i]=0;len=0;
		Rep(i,1,K)siz[a[i]]=1;
	}
	void lqx(int from,int to,int dis)
	{ e[++len].from=from,e[len].to=to,e[len].next=head[from],e[len].dis=dis,head[from]=len;}
	void Dfs(int u,int fa){
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;if(v==fa)continue;
			Dfs(v,u);
			if(siz[v])dp[u]+=dp[v]+e[i].dis;
			siz[u]+=siz[v];
		}
	}

}G;


bool Check(){
	set<int>tmp;
	Rep(i,1,K)tmp.insert(S.Find(a[i]));
	return (tmp.size()==1);
}

void solve(){
	cin>>n>>m>>K;S.Init(n);
	if(K==0)return cout<<"0\n",void();
	Rep(i,1,m)cin>>es[i].from>>es[i].to>>es[i].dis;
	Rep(i,1,K)cin>>a[i];

	sort(es+1,es+m+1);
	Rep(i,1,K)G.siz[a[i]]=1;
	Rep(i,1,m){
		if(S.Find(es[i].from)==S.Find(es[i].to))continue;
		S.Merge(es[i].from,es[i].to);
		G.lqx(es[i].from,es[i].to,es[i].dis);
		G.lqx(es[i].to,es[i].from,es[i].dis);
	}
	G.Dfs(a[1],0);
	ans=min(ans,G.dp[a[1]]);


	do{
		S.Init(n);G.Clear();
		shuffle(es+1,es+m+1,rd);
		Rep(i,1,m){
			if(S.Find(es[i].from)==S.Find(es[i].to))continue;
			S.Merge(es[i].from,es[i].to);
			G.lqx(es[i].from,es[i].to,es[i].dis);
			G.lqx(es[i].to,es[i].from,es[i].dis);
		}
		G.Dfs(a[1],0);
		ans=min(ans,G.dp[a[1]]);
		if(1.0*clock()/CLOCKS_PER_SEC>=0.950)break;
	}while(1);
	cout<<ans<<"\n";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }
正解


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e2+10,maxm=1e3+10,INF=0x3f3f3f3f;

int dp[maxn][1<<11];
int n,m,K;
int a[maxn];

priority_queue<pii>q;

struct Graph{
	struct eg{int from,to,next,dis;}e[maxm];
	int len,head[maxn];bool vis[maxn];
	void lqx(int from,int to,int dis)
	{ e[++len].from=from,e[len].to=to,e[len].next=head[from],e[len].dis=dis,head[from]=len; }
	void Dij(int s){
		memset(vis,0,sizeof(vis));
		while(!q.empty()){
			int u=q.top().sec;q.pop();
			if(vis[u])continue;
			vis[u]=true;
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].to;
				if(dp[v][s]>dp[u][s]+e[i].dis){
					dp[v][s]=dp[u][s]+e[i].dis;
					q.push(mair(-dp[v][s],v));
				}
			}
		}
	}
}G;

void solve(){
	fre(steiner);
	cin>>n>>m>>K;int x,y,w;
	memset(dp,0x3f,sizeof(dp));
	Rep(i,1,m)cin>>x>>y>>w,G.lqx(x,y,w),G.lqx(y,x,w);
	Rep(i,1,K){
		cin>>a[i];
		dp[a[i]][1<<(i-1)]=0;
	}
	for(int s=1;s<(1<<K);++s){
		Rep(i,1,n){
			for(int sub=s&(s-1);sub;sub=s&(sub-1))dp[i][s]=min(dp[i][s],dp[i][sub]+dp[i][s^sub]);
			if(dp[i][s]!=INF){q.push(mair(-dp[i][s],i));}
		}
		G.Dij(s);
	}
	cout<<dp[a[1]][(1<<K)-1];
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

9.9CSP-S短赛2(开小灶2)

T1 元素周期表

看来是一种套路,将每个点对应的x,y坐标连边,在一个联通块内的就是可达的,那么答案就是联通块数减一

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=4e5+10;

struct eg{int from,to,next;}e[maxn*2];
int len,head[maxn];bool vis[maxn];
int ans,n,m,q;
void lqx(int from,int to)
{ e[++len].from=from,e[len].to=to,e[len].next=head[from],head[from]=len; }
void Dfs(int u){
	vis[u]=true;
	for(int i=head[u];i;i=e[i].next)if(!vis[e[i].to])Dfs(e[i].to);
}

void solve(){
	cin>>n>>m>>q;int x,y;
	Rep(i,1,q)cin>>x>>y,lqx(x,n+y),lqx(n+y,x);
	Rep(i,1,n+m)if(!vis[i])++ans,Dfs(i);
	cout<<ans-1;
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

T2 gcd

很是裸。用莫比乌斯函数换gcd,化出来就是

\[ \sum_{d |A} \sum_{i=1}^{n} \mu(d)*[d|a_i]* vis[i] \]

显然每个数贡献的位置只会有\(\log n\)个,统计贡献的位置同样,于是预处理下约数,维护一下每个\(d\)的贡献就完了。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=2e5+10,NN=5e5;

int Mu[NN+10],cnt,prime[maxn];
ll f[NN+10];
bool vis[NN+10];
int n,m;
ll ans=0,num=0;
bool in[maxn];
int a[maxn];
vector<int>vec[NN+10];

void Get(const int N){
	Mu[1]=1;
	for(int i=2;i<=N;i++){
		if(!vis[i])prime[++cnt]=i,Mu[i]=-1;
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++){
			vis[i*prime[j]]=true;
			if(i%prime[j]==0){Mu[i*prime[j]]=0;break;}
			Mu[i*prime[j]]=-Mu[i];
		}
	}
	Rep(i,2,N){
		vec[i].push_back(1);
		for(int j=1;i*j<=N;++j)vec[i*j].push_back(i);
	}
}

void solve(){
	Get(NN);
	cin>>n>>m;
	Rep(i,1,n)cin>>a[i];
/*	while(m--){
		int x;cin>>x;int res=0;
		Rep(i,1,n)if(in[i] && i!=x){
			for(int j=1;j<=5;++j){
				if((a[i]%j==0) && (a[x]%j==0))res+=Mu[j];
			}
		}
		if(!in[x])ans+=res;
		else ans-=res;
		in[x]^=1;
		cout<<ans<<"\n";
	}
*/
	while(m--){
		int x;cin>>x;
		ll res=0;
		if(a[x]==1){
			if(in[x]){
				--num;ans-=num;
				f[1]-=Mu[1];
			}else{
				ans+=num;++num;
				f[1]+=Mu[1];
			}
			in[x]^=1;
			cout<<ans<<"\n";
			continue;
		}
		if(in[x]){
			for(auto it : vec[a[x]]){
				f[it]-=Mu[it];
				res+=f[it];
			}
			ans-=res;in[x]^=1;--num;
		}else{
			for(auto it : vec[a[x]]){
				res+=f[it];
				f[it]+=Mu[it];
			}
			ans+=res;in[x]^=1;++num;
		}
		cout<<ans<<"\n";
	}
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

9.5CSP-S短赛1(开小灶1)

T1ZZH的游戏

实际上把策略想明白就很简单。以一次连续的移动为一个阶段,每个阶段都必定都扩展到一个更小的点,类似贪心。

我们可以每次贪心的把能走的小点都走了,于是我们现在就被一圈大点圈起来了,这时候必定有一个人需要走一个大点来打破僵局,因为我们需要到达1。于是我们贪心的选一个所有备选点的最小的点走(同时另一个人移动到它已经扩展过的最小的点),然后同样的贪心把能走的小点都走了,再考虑大点即可。这样保证每次都是答案尽量小的增长,两边都扩展到了1就停止。所以比较裸的就是直接拿一个优先队列维护所有备选点,这样做是\(O(n \log n)\)的,但是由于那只log是STL造成的,于是开了O2就有无限可能(实际上比线性快)。线性做法就是把优先队列换成桶,从小往大扫就行了。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e6+10;

int Lim,ans;
int n;
int tex;
int Gs,Fs;

struct Graph{
	struct eg{int from,to,next;}e[maxn*2];
	int len,head[maxn];bool vis[maxn];
	priority_queue< int,vector<int>,greater<int> >q;
	void lqx(int from,int to)
	{ e[++len].from=from;e[len].to=to,e[len].next=head[from],head[from]=len; }
	void Clear(){Rep(i,1,n)head[i]=0,vis[i]=0;len=0;priority_queue< int,vector<int>,greater<int> >temp;q=temp;}
	void Extend(int u){
		vis[u]=1;
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].to;if(vis[v])continue;
			q.push(v);
		}
	}
}G,F;

void Sol(){
	while(!G.q.empty() || !F.q.empty()){
		if(G.vis[1] && F.vis[1])break;
		if(G.q.empty()){
			int u=F.q.top();F.q.pop();
			ans=max(ans,Gs+u);Fs=min(Fs,u);
			F.Extend(u);
			continue;
		}
		if(F.q.empty()){
			int u=G.q.top();G.q.pop();
			ans=max(ans,Fs+u);Gs=min(Gs,u);
			G.Extend(u);
			continue;
		}
		int u=G.q.top(),v=F.q.top();
		if(Gs+v<Fs+u){
			F.q.pop();
			ans=max(ans,Gs+v);Fs=min(Fs,v);
			F.Extend(v);
		}
		else{
			G.q.pop();
			ans=max(ans,Fs+u);Gs=min(Gs,u);
			G.Extend(u);
		}
	}
	cout<<ans<<"\n";
}

void solve(){
	cin>>n;G.Clear(),F.Clear();int x,y;
	Rep(i,2,n)cin>>x>>y,G.lqx(x,y),G.lqx(y,x);
	Rep(i,2,n)cin>>x>>y,F.lqx(x,y),F.lqx(y,x);
	cin>>Gs>>Fs;ans=Gs+Fs;
	G.Extend(Gs),F.Extend(Fs);
	Sol();
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>tex;while(tex--)solve(); }


T2ZZH的背包

稍劣一点的是5e8的双指针,考虑折半把两边的所有方案都预处理出来然后合并,就是枚举其中一边的选法,然后在另一边二分查找合法区间,然后由于两边都是有序的,所以左右边界都是单调不升的,双指针即可。

正解是1e7的神奇做法,首先预处理出一边的方案,用归并达到\(O(n)\),然后枚举另一边的每个物品选不选。把询问离线下来拆成两个小于等于,然后在一边选就相当于在另一边的询问变小了,把所有在另一边的询问跑出来,然后单指针扫一遍统计贡献就行了。由于做法诡异需要卡空间。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e2+10;

int n,q;
ll v[maxn];
ll a[1<<20|1],b[1<<20|1];
int na,nb,Va,Vb;

void solve(){
	cin>>n>>q;Rep(i,1,n)cin>>v[i];
	na=n>>1,nb=n-na;
	Va=1<<na,Vb=1<<nb;
	for(int i=0;i<Va;++i) for(int j=0;j<na;++j)a[i+1]+=((i>>j)&1)*v[j+1];
	for(int i=0;i<Vb;++i) for(int j=0;j<nb;++j)b[i+1]+=((i>>j)&1)*v[na+j+1];
	sort(a+1,a+Va+1),sort(b+1,b+Vb+1);
	while(q--){
		ll l,r;cin>>l>>r;ll ans=0;
		int pl=lower_bound(b+1,b+Vb+1,l)-b,pr=upper_bound(b+1,b+Vb+1,r)-b-1;
		if(pr>=pl)ans+=pr-pl+1;
		Rep(i,2,Va){
	//		while(pl<=n && a[i]+b[pl]<l)++pl;
			while(pl>1 && a[i]+b[pl-1]>=l)pl--;
	//		while(pr<n && a[i]+b[pr]<r)++pr;
			while(pr>=1 && a[i]+b[pr]>r)pr--;
			if(pr>=pl)ans+=pr-pl+1;
		}
		cout<<ans<<"\n";
	}
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

9.4CSP-S模拟2

T1谜之阶乘

下降幂的长度最长是二十,于是可以枚举长度,二分下降幂的起点找。

另一种是长度为d的下降幂一定在n开d次根的附近浮动,于是直接暴力\(d^2\)判一遍就行,注意pow的精度问题

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=1e18;

int tex;
int n;

vector<pii>ans;

void Check(int n,int x){
	int lim=pow(n,1.0/x);
	for(int i=max(2LL,lim-x);i<=lim;++i){
		int now=1;
		for(int j=i;j<=lim+x;++j){
			now*=j;if(now>n || now<0)break;
			if(now==n){ans.emplace_back(j,i-1);break;}
		}
	}
}

void solve(){
	cin>>n;if(n==1)return cout<<"-1\n",void();
	ans.clear();
	Dwn(i,14,1)Check(n,i);
	ans.emplace_back(n,n-1);
	sort(ans.begin(),ans.end());
	ans.erase(unique(ans.begin(),ans.end()),ans.end());
	cout<<ans.size()<<"\n";
	for(auto it : ans)cout<<it.fir<<" "<<it.sec<<"\n";
}

#undef int
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>tex;while(tex--)solve(); }

T2子集

构造题,在子集siz为偶数时有较好的性质,于是考虑把奇数情况变成偶数即可,单独处理前三个k,将前两个k配对构造成公差为1的等差数列,与第三列倒序匹配,那么前三列就解决了,于是问题退化成偶数情况。判一下非法就行了

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=1e6+10;

int tex,n,K,siz;

void Work1(){
	cout<<"Yes\n";
	int now=0;int lim=n>>1;
	Rep(i,1,lim){
		cout<<i<<" "<<n-i+1<<" ";
		now+=2;
		if(now==siz){now=0;cout<<"\n";}
	}
}

bool cmp(pii a,pii b){return a.fir+a.sec>b.fir+b.sec;}

pii a[maxn];

void Work2(){
	cout<<"Yes\n";
	Rep(i,1,K)a[i].fir=(i+(K>>1)-1)%K+1,a[i].sec=i+K;
	sort(a+1,a+K+1,cmp);
	int pl=3*K+1,pr=n;
	Rep(i,1,K){
		cout<<a[i].fir<<" "<<a[i].sec<<" "<<2*K+i<<" ";
		int now=3;
		while(now<siz){
			cout<<pl<<" "<<pr<<" ";++pl,--pr;
			now+=2;
		}
		cout<<"\n";
	}
}

void solve(){
	cin>>n>>K;
	siz=n/K;
	if(n==1)return cout<<"Yes\n1\n",void();
	if(siz==1)return cout<<"No\n",void();
	if(siz%2==0)return Work1();
	if(!(K&1))cout<<"No\n",void();
	else return Work2();
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>tex;while(tex--)solve(); }

T3混凝土粉末

发现每次修改操作的编号与高度都是单增的,于是考虑二分答案,找到这个位置上第一次超过阈值的时刻。常数较大且空间复杂度错误的做法是主席树,可以擦边拿到89分或95分(有一个点看常数和运气)。正解就是把主席树换掉,把操作离线下来,像扫描线一样差分,然后只在一颗树上干就行了,树状数组上二分不太会,但是6秒显然是想把两只log放过去。写线段树就一只log了

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;

const int maxn=1e6+10;

#define gc if(++ip==ie)fread(ip=buf,1,SZ,stdin)
static const int SZ=1<<22;char buf[SZ],*ie=buf+SZ,*ip=ie-1;
inline int read(){ gc;while(*ip<'-')gc; bool f=*ip=='-';if(f)gc; int x=*ip&15;gc; while(*ip>'-'){x*=10;x+=*ip&15;gc;} return f ? -x : x; }
inline ll readL(){ gc;while(*ip<'-')gc; bool f=*ip=='-';if(f)gc; ll x=*ip&15;gc; while(*ip>'-'){x*=10;x+=*ip&15;gc;} return f ? -x : x; }

int n,q;
int chk[maxn];
bool vis[maxn];
int cnt;
struct tumplex{
	int fir,sec,thi;
	tumplex(){}
	tumplex(int a,int b,int c):fir(a),sec(b),thi(c){}
};

vector<tumplex>vec[maxn],que[maxn];
int ans[maxn];

struct BIT{
	#define lowbit(x) (x&-x) 
	int c[maxn];
	void Add(int x,int d){for(;x<=q;x+=lowbit(x))c[x]+=d;}
	int Query(int x){int res=0;for(;x;x-=lowbit(x))res+=c[x];return res;}
}T;

int tot;

int Sol(int x,ll w){
	int l=1,r=x,ans=0;
	while(l<=r){
		int mid=(l+r)>>1;
		if(T.Query(mid)>=w)ans=mid,r=mid-1;
		else l=mid+1;
	}
	return ans;
}

void solve(){
	n=read(),q=read();int opt,x,y;
	ll w;
	Rep(i,1,q){
		opt=read();
		if(opt==1){
			x=read(),y=read(),w=readL();
			chk[++chk[0]]=i;
			vec[x].emplace_back(chk[0],w,i),vec[y+1].emplace_back(chk[0],-w,i);
		}else{ x=read(),w=readL();que[x].emplace_back(++tot,w,i); }
	}
	Rep(i,1,n){
		for(auto it : vec[i])T.Add(it.thi,it.sec);
		for(auto it : que[i])ans[it.fir]=Sol(it.thi,it.sec);
	}
	Rep(i,1,tot)printf("%d\n",ans[i]);
}
#undef int
int main (){ return solve(),0; }

T4排水系统

其实挺简单的,主要是想明白转移的意义。不难设出经过断边和没经过断边两种状态,发现我们必须要一条边断掉,于是我们在不断边的基础上进行流的修正即可。

点击查看代码


#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i) 
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;

const int maxn=2e5+10,maxm=5e5+10,Mod=998244353;

int pw(int x,int p){int res=1,base=x;while(p){if(p&1)res=1LL*res*base%Mod;base=1LL*base*base%Mod;p>>=1;}return res;}
int Inv(int x){return pw(x,Mod-2);}

int f[maxn][2];
int n,m,r,k;
int inv[maxm];
int nowany,nownone,P,iP;

struct Graph{
	struct eg{int from,to,next,a,p;}e[maxm];
	int len,head[maxn];int ins[maxn],ous[maxn],vis[maxn];
	void lqx(int from,int to,int a)
	{ ++ins[to],++ous[from];e[++len].from=from,e[len].to=to,e[len].next=head[from],e[len].a=a,head[from]=len; }
	queue<int>q;
	void TopoDp(){
		Rep(i,1,len)e[i].p=1LL*e[i].a*iP%Mod;
		Rep(i,1,m)q.push(i),f[i][0]=f[i][1]=1;
		while(!q.empty()){
			int u=q.front();q.pop();
			cerr<<u<<" \n";
			int nany=0;
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].to;
				nany=(nany+e[i].p)%Mod,++vis[v];
				if(vis[v]==ins[v])q.push(v);
			}
			for(int i=head[u];i;i=e[i].next){
				int v=e[i].to;
				f[v][0]=(f[v][0]+1LL*f[u][0]*inv[ous[u]])%Mod;
				f[v][1]=(f[v][1]+1LL*f[u][0]*inv[ous[u]]%Mod*(nany-e[i].p+Mod)%Mod*inv[ous[u]-1])%Mod;
				f[v][1]=(f[v][1]+1LL*f[u][1]*inv[ous[u]])%Mod;
				f[v][1]=(f[v][1]+1LL*(Mod-1)*f[u][0]%Mod*inv[ous[u]]%Mod*e[i].p)%Mod;
			}
		}
	}
}G;

void solve(){
	cin>>n>>m>>r>>k;int x,y,w;inv[0]=inv[1]=1;
	Rep(i,2,k)inv[i]=1LL*(Mod-Mod/i)*inv[Mod%i]%Mod;
	Rep(i,1,k)cin>>x>>y>>w,P=(P+w)%Mod,G.lqx(x,y,w);
	iP=Inv(P);G.TopoDp();
	Rep(i,n-r+1,n)cout<<f[i][1]<<" ";
}

int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }

posted @ 2022-09-14 19:45  Delov  阅读(242)  评论(1编辑  收藏  举报