NOIP2020 题解

排水系统(water)

  • 先把前 \(m\) 个的权值设为 \(1\)

  • 拓扑排序,将它的权值平分给它所有的出点。

  • 输出所有出度为 \(0\) 的点的权值。

  • 这个思路没有错,但题目里说起点到终点中间点数不超过 \(10\),也就是分母可以达到 \(60^{11}\)

  • 这样就要写高精度,但是高精度 \(\gcd\) 不是一件容易的事情。

  • 有这么一种处理方法:钦定所有权值的分母都为 \(60^{11}\),到最后再约分。因为分母一定是 \(60^{11}\) 的约数。

  • 那么,过程中只要做高精度除单精度,高精度加高精度即可。

  • 最后约分的话,因为分母是 \(2^{22}\times 3^{11}\times 5^{11}\),直接判断原数是不是 \(2,3,5\) 的倍数,试除即可。

  • 高精度可以直接压 \(10^{11}\) 进制,那么这个高精度数的四则运算是 \(O(1)\) 的。(只需两个 \(\texttt{long long}\) 存)

  • 代码并不长。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll lim=100000000000ll;//1e11
int n,m,d[110000],ind[110000],v[110000][6];
queue<int> que;
struct node{
	//x%lim, x/lim
	ll v[2];
	node(){
		v[0]=0ll; v[1]=0ll;
	}
	void work(){
		v[0]=0ll; v[1]=362797056ll;
	}
	void div(int x){
		v[0]=((v[1]%x)*lim+v[0])/x;
		v[1]=v[1]/x;
	}
	void add(const node &x){
		v[0]+=x.v[0]; v[1]+=x.v[1];
		if (v[0]>=lim) v[0]-=lim,v[1]++;
	}
	bool check2() const{ return !(v[0]&1);}
	bool check3(){ return !((v[0]+v[1])%3);}
	bool check5(){ return !(v[0]%5);}
	void print(){
		if (v[1]) printf("%lld%011lld",v[1],v[0]);
		else printf("%lld",v[0]);
	}
} a[110000];
void print(node x){
	node y; y.work();
	if (!x.v[0]&&!x.v[1]){ puts("0 1"); return;}//这里其实不用特判
	for (int i=1;i<=22;i++)
		if (x.check2()){
			x.div(2);
			y.div(2);
		} else break;
	for (int i=1;i<=11;i++)
		if (x.check3()){
			x.div(3);
			y.div(3);
		} else break;
	for (int i=1;i<=11;i++)
		if (x.check5()){
			x.div(5);
			y.div(5);
		} else break;
	x.print(); putchar(' '); y.print(); putchar('\n');
}
int main(){
//	freopen("water.in","r",stdin);
//	freopen("water.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++) a[i].work();
	for (int u=1;u<=n;u++){
		scanf("%d",&d[u]);
		for (int i=0;i<d[u];i++){
			scanf("%d",&v[u][i]);
			ind[v[u][i]]++;
		}
	}
	
	for (int i=1;i<=n;i++)
		if (!ind[i]) que.push(i);
	while (!que.empty()){
		int u=que.front(); que.pop();
		if (!d[u]) continue;
		a[u].div(d[u]);
		
		for (int i=0;i<d[u];i++){
			a[v[u][i]].add(a[u]);
			ind[v[u][i]]--;
			if (!ind[v[u][i]]) que.push(v[u][i]);
		}
	}
	
	for (int i=1;i<=n;i++)
		if (!d[i]) print(a[i]);
	return 0;
}

字符串匹配(string)

  • 枚举 \(AB\) 的长度 \(len\)。用桶维护当前 \(F(A)=0,1,2,\cdots,26\) 有多少个。
  • 在从左往右扫的过程中,二分求出 \(i_{\max}\) 表示最大的 \(i\) 满足原串可以表示为 \((AB)^iC\)。(\(AB\) 的长为 \(len\)
  • 那么,对于 \(i=1,3,5,\cdots\),它们对应的 \(F(C)\) 都一样;对于 \(i=2,4,6,\cdots\),它们对应的 \(F(C)\) 也一样。
  • 可以直接求前缀和计算答案,但是维护前缀和不能用树状数组,这样会多一个 \(\log\)。可以用两个指针分别维护 \(i\) 为奇数和偶数的前缀和,每次,奇数的指针,\(F(C)\) 的变化量为 \(1\),偶数的指针变化量为 \(0/2\),所以是常数复杂度。
  • 总时间复杂度为 \(O(T\sum_{i=1}^n \log(\frac n i))=O(Tn)\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Mod1=998244353;
const int Mod2=1e9+7;
const int bas1=233;
const int bas2=23333;
int T,n,tot[31],num[31],F[1100000]; char s[1100000];
ll hs0[1100000],hs1[1100000],p0[1100000],p1[1100000],ans;
inline bool check(int l0,int r0,int l1,int r1){
	return (hs0[r0]+(Mod1-p0[r0-l0])*hs0[l0])%Mod1==(hs0[r1]+(Mod1-p0[r1-l1])*hs0[l1])%Mod1
		&& (hs1[r0]+(Mod2-p1[r0-l0])*hs1[l0])%Mod2==(hs1[r1]+(Mod2-p1[r1-l1])*hs1[l1])%Mod2;
}
int pos0,s0,pos1,s1;
inline void add(int x){
	num[x]++;
	if (x<=pos0) s0++;
	if (x<=pos1) s1++;
}
inline int gets0(int x){
	while (pos0<x) pos0++,s0+=num[pos0];
	while (pos0>x) s0-=num[pos0],pos0--;
	return s0;
}
inline int gets1(int x){
	while (pos1<x) pos1++,s1+=num[pos1];
	while (pos1>x) s1-=num[pos1],pos1--;
	return s1;
}
void initF(){
	for (int i=0;i<=26;i++) tot[i]=0;
	int tmp=0;
	for (int i=n;i>=1;i--){
		tmp-=tot[s[i]-'a'];
		tot[s[i]-'a']^=1;
		tmp+=tot[s[i]-'a'];
		F[i]=tmp;
	}
}
void solve(){
	pos0=0; s0=0; pos1=0; s1=0;
	for (int i=0;i<=26;i++) tot[i]=0,num[i]=0;
	
	int tmp=1; tot[s[1]-'a']=1;
	ans=0;
	for (int i=2;i<n;i++){
		add(tmp);
		
		tmp-=tot[s[i]-'a'];
		tot[s[i]-'a']^=1;
		tmp+=tot[s[i]-'a'];
		
		int l=0,r=(n-1)/i-1;
		while (l<r){
			int mid=(l+r+1)>>1;
			if (check(0,mid*i,i,(mid+1)*i)) l=mid;
			else r=mid-1;
		}
		
		ans+=gets0(F[i+1])*(r/2+1);
		if (2*i+1<=n) ans+=gets1(F[2*i+1])*((r+1)/2);
	}
}
int main(){
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	p0[0]=1; p1[0]=1;
	for (int i=1;i<=1000005;i++) p0[i]=p0[i-1]*bas1%Mod1;
	for (int i=1;i<=1000005;i++) p1[i]=p1[i-1]*bas2%Mod2;
	scanf("%d",&T);
	while (T--){
		scanf("%s",s+1); n=(int)strlen(s+1);
		for (int i=1;i<=n;i++){
			hs0[i]=(hs0[i-1]*bas1+(s[i]-'a'+1))%Mod1;
			hs1[i]=(hs1[i-1]*bas2+(s[i]-'a'+1))%Mod2;
		}
		initF(); solve();
		printf("%lld\n",ans);
	}
	return 0;
}

移球游戏(ball)

  • 分治。假设现在 \([l,r]\) 的球在 \([l,r]\) 的柱子上,要让 \([l,mid]\) 的球在 \([l,mid]\) 的柱子上。
  • 如果可以使得 \([l,mid]\) 中那些 \([mid+1,r]\) 的球放到当前堆的最上面,\([mid+1,r]\) 中那些 \([l,mid]\) 的球放到当前堆的最上面,那么就可以通过 \(n+1\) 堆两两交换,满足条件。
  • 有一种方法,在不改变其它堆的球情况下,把当前堆所有“要移动的球”移到最上面。
  • 具体做法,先随便取一个不为当前堆的临时堆 \(tmp\)
  1. 统计当前堆要放到最上面的球的个数 \(a\)
  2. 判断是否需要移动,即是否要移的已经在最上面(其实这步也可以不需要)。
  3. \(tmp\) 中移 \(a\) 个球到 \(n+1\)。(现在 \(tmp\) 空余 \(a\) 个位置,\(n+1\) 空余 \(m-a\) 个位置)
  4. 将当前堆的球依次移出,其中“要移动的球”放到 \(tmp\),其它球放到 \(n+1\),直到当前堆为空。(现在 \(tmp\)\(n+1\) 满了)
  5. \(n+1\) 中最上面的 \(m-a\) 个球移回当前堆。(这些球显然是原来当前堆的球,\(n+1\) 还剩 \(a\) 个球)
  6. \(tmp\) 中最上面的 \(a\) 个球移回当前堆。(这些球显然是原来当前堆的球,\(tmp\) 还剩 \(m-a\) 个球,当前堆满了)
  7. \(n+1\)\(a\) 个球移回当前堆。(这些球显然是原来当前堆的球,\(tmp\) 满了,\(n+1\) 空了)
  • 分析一下可以发现,当前堆要移的球到了当前堆的最上面,其它堆没有变化(\(tmp\) 中的球也是按移出的顺序移回的)。
  • 之后,就可以枚举右边的每一个堆,让它和左边的堆的球交换即可。这可以简单的实现。
  • 那么就可以分治下去做这个过程了。当 \(l=r\) 时,则代表颜色为 \(l\) 的球已经在柱子 \(l\) 上,直接返回即可。
  • 操作次数/时间复杂度为 \(O(n\log n\times m)\)
#include<bits/stdc++.h>
using namespace std;
int n,m,a[55][410],sz[55],tot[55];
int ans,Ansx[1100000],Ansy[1100000];
void mymove(int x,int y,int k=1){
	while (k--){
		ans++; Ansx[ans]=x; Ansy[ans]=y;
		sz[y]++;
		a[y][sz[y]]=a[x][sz[x]];
		sz[x]--;
	}
}
void work(int x,int y){
	if (tot[x]>tot[y]) swap(x,y);
	mymove(y,n+1,tot[y]);
	mymove(x,y,tot[x]);
	mymove(n+1,x,tot[x]);
	mymove(n+1,y,tot[y]-tot[x]);
	tot[y]-=tot[x]; tot[x]=0;
}
void solve(int l,int r){
	if (l==r) return;
	int mid=(l+r)>>1,tmp;
	for (int i=l;i<=r;i++){
		tmp=(i==l?r:l); tot[i]=0;
		for (int j=m;j>=1;j--)
			if ((i<=mid)!=(a[i][j]<=mid)) tot[i]++;
		mymove(tmp,n+1,tot[i]);
		for (int j=m;j>=1;j--)
			if ((i<=mid)!=(a[i][j]<=mid)) mymove(i,tmp);
			else mymove(i,n+1);
		mymove(n+1,i,m-tot[i]); mymove(tmp,i,tot[i]); mymove(n+1,tmp,tot[i]);
	}
	int j=l;
	for (int i=mid+1;i<=r;i++)
		while (tot[i]){
			while (j<=mid&&!tot[j]) j++;
			work(i,j);
		}
	solve(l,mid); solve(mid+1,r);
}
int main(){
//	freopen("ball.in","r",stdin);
//	freopen("ball.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++)
			scanf("%d",&a[i][j]);
		sz[i]=m;
	}
	solve(1,n);
	printf("%d\n",ans);
	for (int i=1;i<=ans;i++) printf("%d %d\n",Ansx[i],Ansy[i]);
	return 0;
}

微信步数(walk)

  • 首先,如果在第一轮或第二轮直接走完,那么模拟即可。
  • 如果模拟两轮之后没有走完,记第一轮结束时,每一维合法的坐标数分别为 \(a_i\),第二轮结束时,每一维合法的坐标数分别为 \(b_i\),那么第 \(k(k\geq 2)\) 轮结束后,每一维合法的坐标数分别为 \(b_i-(a_i-b_i)\times k\)
  • 那么,枚举 \(i\),表示现在计算在第 \(t+2(t\geq 1)\) 轮(满足 \(t+2\) 轮后仍合法)第 \(i\) 步之后的答案之和。
  • 然后式子就类似于 \(\sum_{x=1}^t\prod_j (tot[j][i]-(a[j]-b[j])*x)\)。其中 \(tot[j][i]\) 表示第一轮做完后,第二轮做了 \(i\) 步,第 \(j\) 维剩下的合法坐标数。
  • 这个 \(t\) 是可以计算出来的,即 \(\min_{j,b[j]\neq 0} \frac{a[j][i]}{b[j]}\)
  • \(x\) 看做一个变量,求出 \(f(x)=\prod_j (tot[j][i]-(a[j]-b[j])*x)\) 的表达式,设其为 \(f(x)=\sum_i c_ix^i\),那么答案即为 \(\sum_i c_i(\sum_{x=1}^tx^i)\),后面这个是个自然数幂之和的形式。
  • \(k\leq 3\) 时,自然数幂和有简洁的式子。
  • \(k>3\) 时,那么 \(t\leq \max w_i=10^6\),可以直接预处理自然数幂和。
  • 总时间复杂度 \(O(nk^2)\),可以优化求 \(f(x)\) 的过程(即每次的变化量极少)可以做到 \(O(nk)\)
  • 只给出 \(O(nk^2)\) 的实现。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int Mod=1e9+7;
const int inv2=(Mod+1)/2;
const int inv6=(Mod+1)/6;
int n,k,w[1100000],c[1100000],d[1100000];
int b[1100000],a[11][1100000];
int L[1100000],R[1100000],l[1100000],r[1100000];
ll ans,num[11][1100000],f[1100000],g[1100000];
ll qpow(ll x,int a){
	ll res=1;
	while (a){
		if (a&1) res=res*x%Mod;
		x=x*x%Mod; a>>=1;
	}
	return res;
}
inline ll sqr(ll x){ return x*x%Mod;}
ll calc(int k,ll x){//1^k+2^k+3^k+...+x^k
	switch (k){
		case 0: return x;
		case 1: return (x*(x+1)%Mod*inv2%Mod);
		case 2: return (x*(x+1)%Mod*(x<<1|1)%Mod*inv6%Mod);
		case 3: return sqr(x*(x+1)%Mod*inv2%Mod);
		default: return num[k-4][x];
	}
}
void init(){
	ll tmp;
	for (int i=1;i<=1000000;i++){
		tmp=sqr(i); tmp=sqr(tmp);
		num[0][i]=(num[0][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
		num[1][i]=(num[1][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
		num[2][i]=(num[2][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
		num[3][i]=(num[3][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
		num[4][i]=(num[4][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
		num[5][i]=(num[5][i-1]+tmp)%Mod; tmp=tmp*i%Mod;
		num[6][i]=(num[6][i-1]+tmp)%Mod;
	}
}
bool flag;
int main(){
//	freopen("walk.in","r",stdin);
//	freopen("walk.out","w",stdout);
	init();
	scanf("%d%d",&n,&k); ll now=1;
	for (int i=1;i<=k;i++){
		scanf("%d",&w[i]);
		L[i]=1; R[i]=w[i];
		now=now*w[i]%Mod;
	}
	for (int i=1;i<=n;i++) scanf("%d%d",&c[i],&d[i]);
	bool flag=false;
	ans=now;
	for (int i=1;i<=n;i++){
		now=now*qpow(R[c[i]]-L[c[i]]+1,Mod-2)%Mod;
		L[c[i]]+=d[i]; R[c[i]]+=d[i];
		L[c[i]]=max(L[c[i]],1); R[c[i]]=min(R[c[i]],w[c[i]]);
		if (L[c[i]]>R[c[i]]){
			flag=true;
			break;
		}
		now=now*(R[c[i]]-L[c[i]]+1)%Mod;
		ans=(ans+now)%Mod;
	}
	if (flag){
		printf("%lld\n",ans);
		return 0;
	}
	
	
	for (int i=1;i<=k;i++) b[i]=R[i]-L[i]+1;
	
	flag=false;
	for (int i=1;i<=n;i++){
		now=now*qpow(R[c[i]]-L[c[i]]+1,Mod-2)%Mod;
		L[c[i]]+=d[i]; R[c[i]]+=d[i];
		L[c[i]]=max(L[c[i]],1); R[c[i]]=min(R[c[i]],w[c[i]]);
		if (L[c[i]]>R[c[i]]){
			flag=true;
			break;
		}
		now=now*(R[c[i]]-L[c[i]]+1)%Mod;
		for (int j=1;j<=k;j++) a[j][i]=R[j]-L[j]+1;
		ans=(ans+now)%Mod;
	}
	if (flag){
		printf("%lld\n",ans);
		return 0;
	}
	
	for (int i=1;i<=k;i++){
		b[i]-=(R[i]-L[i]+1);
		if (b[i]) flag=true;
	}
	if (!flag){
		puts("-1");
		return 0;
	}
	
	for (int i=1;i<=n;i++){
		//\prod_j a[j][i]-b[j]  +  \prod_j a[j][i]-b[j]*2  + ... + a[j][i]-b[j]*t
		//a[j][i]-b[i]*t=0   ==>   t=a[j][i]/b[j]
		int tmp=INF;
		for (int j=1;j<=k;j++)
			if (b[j]) tmp=min(tmp,a[j][i]/b[j]);
		//\prod_j a[j][i]-b[j]*x
		
		f[0]=1; for (int j=1;j<=k;j++) f[j]=0;
		for (int j=1;j<=k;j++){
			for (int t=0;t<j;t++){
				g[t]=(g[t]+f[t]*a[j][i])%Mod;//long long !!!
				g[t+1]=(g[t+1]+f[t]*(Mod-b[j])%Mod)%Mod;
			}
			for (int t=0;t<=j;t++) f[t]=g[t],g[t]=0;
		}
		
		for (int j=0;j<=k;j++) ans=(ans+f[j]*calc(j,tmp))%Mod;//long long !!!
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-12-09 22:13  hydd  阅读(670)  评论(0编辑  收藏  举报