Pbri

正睿noip20天冲刺

正睿noip20天冲刺

2021-10-12

A

题意

你有一个素数 \(p\) 和二元组 \((a,b)\) ,满足 \(p\nmid a+b\) ,你可以对这个二元组进行若干次操作,让他变成另一个二元组 \((c,d)\) ,操作有两种:

\(\bullet\,\,\,(a,b)\rightarrow(2a,(b-a+p) \mod p)\)

\(\bullet\,\,\,(a,b)\rightarrow ((a-b+p)\mod p,2b)\)

\(q\) 次询问,每次给定 \(a,b,c,d\) 问最少经过多少次操作可以得到 \((c,d)\) ,如果不能得到输出 \(-1\)

\(p\le 10^9+7,q\le 10^5,0\le a,b,c,d\le p-1,p\in prime,p\nmid a+b\)

题解

显然这两种操作都不改变在模意义下的和,所以可以根据这个把 \(-1\) 判掉,还有就是 \((a,b)=(0,0),(c,d)\ne(0,0)\) 的情况也要判一下。接下来我们只需要考虑 \(a\) 什么时候变成 \(d\) 即可。

然后我们考虑这个 \((a,b)\) 经过操作之后会变成什么,经过手摸发现(可归纳证明):

\[(a,b)\rightarrow (2a,b-a),(a-b,2b)\rightarrow (4a,...),(3a-b,...),(2a-2b,...),(a-3b,...) \]

发现经过 \(k\) 步操作后形成的 \(c\) 都满足:令 \(c\equiv am-bn \mod p\) ,则 \(m+n=2^k\)

于是我们只需要找出满足这个方程并且满足后面这个条件,最小的 \(k\) 。然后我们可以用 \(2^k-m\) 来代替 \(n\) ,得到

\[c\equiv am-bn\mod p\\c\equiv(a+b)m-2^kb\mod p\\令 a+b=s,x=m,可以得到形如\\sx+py=c+2^kb \]

求最小的 \(k\) 满足存在一个 \(x\in(0,2^k]\) 的解。因为 \(s\nmid p\) ,所以可以根据扩欧求出一组等于 \(1\) 的解,然后暴力枚举 \(k\) ,然后根据一些操作把 \(x\) 尽量小,然后特判一下等于 \(0\)\(p\le 2^k\) 的情况,就可以了。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <map>
#include <queue>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 100010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
ll p;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
	if(a<b) return exgcd(b,a,y,x); 
	if(!b)
	{
		x=1,y=0;
		//printf("## %lld %lld %lld %lld\n",a,b,x,y);
		return a;
	}
	ll gcd=exgcd(b,a%b,x,y),tmp=x;
	//printf("a=%lld b=%lld x=%lld y=%lld tmp=%lld\n",a,b,x,y);
	x=y,y=tmp-(a/b)*y;
	return gcd;
}
ll q,a,b,c,d,x,y;
void solve()
{
	a=read(),b=read(),c=read(),d=read();
	if((a+b)%p!=(c+d)%p){puts("-1");return;}
	if(a==c&&b==d){puts("0");return;}
	if(!a&&!b){puts("-1");return;}
	ll base=1;
	ll x=0,y=0;
	exgcd(a+b,p,x,y);
	x%=p;
	if(x<=0) x+=p;
	y=(1-x*(a+b))/p;
	//printf("x=%lld y=%lld\n",x,y);
	FUP(i,1,62)
	{
		base<<=1;
		ll tmp=(base%p*b%p+c)%p*x%p;
		//printf("tmp=%lld\n",tmp);
		if((tmp||(tmp==0&&p<=base))&&tmp<=base){printf("%d\n",i);return;}
	}
	puts("-1");
}
int main(){
	//freopen("Adata.in","r",stdin);
	//freopen("Azj.out","w",stdout);
	p=read(),q=read();
	while(q--) solve();
	return 0;
}

B

题意

给定 \(n\) 个在 \([1,200000]\) 内随机生成的整数,最多去掉 \(k\) 个数,使得可以将剩下的元素分成不交的两个集合,满足这两个集合的和相等。如果存在解请构造方案,不存在输出 \(-1\)

\(n\le 2\times 10^5,\min(25,n-2)\le k\le n-2\)

题解

考虑 \(n\le 25\) 的情况,先不考虑不交,我们找到两个不同的集合满足这两个集合的和相等,然后再把这两个集合的交去掉,依然满足条件,时间复杂度:\(O(2^n)\)

再考虑 \(n>25\) 的情况,先把前 \(25\) 个数去掉,对于后面的数,令\(A\) 等于第一个集合的元素之和,\(B\) 同理,我们维护一个 \(\Delta=A-B\) ,初始 \(\Delta=0\) ,然后对于新来的数 \(x\) ,如果 \(\Delta>0\) ,那么 \(\Delta\leftarrow\Delta-x\) ,否则 \(\Delta\leftarrow \Delta+x\) 。然后我们接下来只需要在前 \(25\) 个数中找到两个不交的集合满足差为 \(\Delta\) 。因为 \(2^{25}>>25\times 200000\) ,所以在随机的情况下,有很大概率存在两个集合满足差为 \(\Delta\) ,然后对于不交的限制处理方法是一样的,去掉交的部分。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <bitset>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 200010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
const int W=200000;
int n,K;
ll a[MAXN];
int lbt(int x){return x&(-x);}
namespace solve1{
	int sum[1<<25],mk[25*MAXN],id[1<<25];
	void solve()
	{
		FUP(i,0,n-1) id[1<<i]=i+1;
		FUP(i,1,(1<<n)-1)
		{
			sum[i]=sum[i^lbt(i)]+a[id[lbt(i)]];
			//cout<<(bitset<9>)i;
			//printf(" :lbt=%d id=%d sum=%d\n",lbt(i),id[lbt(i)],sum[i]);
			if(mk[sum[i]])
			{
				int a=mk[sum[i]],b=i;
				int x=a^(a&b),y=b^(a&b);
				printf("%d ",__builtin_popcount(x));
				FUP(i,1,n) if((x>>(i-1))&1) printf("%d ",i);puts("");
				printf("%d ",__builtin_popcount(y));
				FUP(i,1,n) if((y>>(i-1))&1) printf("%d ",i);puts("");
				return;
			}
			mk[sum[i]]=i;
		}
		puts("-1");
	}
}
namespace solve2{
	ll delta;
	int cnt1,cnt2;
	bool bel[MAXN];
	int mk[25*MAXN],sum[1<<25],id[1<<25];
	void solve()
	{
		FUP(i,26,n)
		{
			if(delta>0) bel[i]=1,cnt2++,delta-=a[i];
			else bel[i]=0,cnt1++,delta+=a[i];
		}
		FUP(i,0,24) id[1<<i]=i+1;
		//printf("delta=%lld\n",delta);
		FUP(i,1,(1<<25)-1)
		{
			sum[i]=sum[i^(lbt(i))]+a[id[lbt(i)]];
			//printf("i=%d sum=%d\n",i,sum[i]);
			if(sum[i]-delta>=0&&sum[i]-delta<=25*W)
			{
				if(mk[sum[i]-delta])
				{
					int a=mk[sum[i]-delta],b=i;
					int x=a^(a&b),y=b^(a&b);
					printf("%d ",__builtin_popcount(x)+cnt1);
					FUP(i,1,25) if((x>>(i-1))&1) printf("%d ",i);
					FUP(i,26,n) if(!bel[i]) printf("%d ",i);puts("");
					printf("%d ",__builtin_popcount(y)+cnt2);
					FUP(i,1,25) if((y>>(i-1))&1) printf("%d ",i);
					FUP(i,26,n) if(bel[i]) printf("%d ",i);puts("");
					return;
				}
			}
			if(sum[i]+delta>=0&&sum[i]+delta<=25*W)
			{
				if(mk[sum[i]+delta])
				{
					int a=mk[sum[i]+delta],b=i;
					int x=b^(a&b),y=a^(a&b);
					printf("%d ",__builtin_popcount(x)+cnt1);
					FUP(i,1,25) if((x>>(i-1))&1) printf("%d ",i);
					FUP(i,26,n) if(!bel[i]) printf("%d ",i);puts("");
					printf("%d ",__builtin_popcount(y)+cnt2);
					FUP(i,1,25) if((y>>(i-1))&1) printf("%d ",i);
					FUP(i,26,n) if(bel[i]) printf("%d ",i);puts("");
					return;
				}
			}
			mk[sum[i]]=i;
		}
		puts("-1");
	}
}
int main(){
	//freopen("Bdata.in","r",stdin);
	n=read(),K=read();
	FUP(i,1,n) a[i]=read();
	if(n<=25) solve1::solve();
	else solve2::solve();	
	return 0;
}

C

题意

给一棵有 \(n\) 个点的以 \(1\) 为根的树,每个点有权值,给定一个值 \(k\) ,如果一个结点的直属儿子数量不小于 \(k\) ,那么你可以知道所有直属儿子的权值之和。如果一个结点的子树大小去掉自己有不小于 \(k\) 个结点,那么你可以知道子树内不包括自己的所有节点的权值之和。显然,你可以通过一些方式求得一些点的权值。另外,你还可以删除一些叶子。如果一个结点的所有儿子都被删完,那么他也可以删。求:删掉若干个点之后最多能知道多少个结点的权值。 \(n\le 8\times 10^5\)

题解

我们定义第一种叫儿子之和,第二种叫子树之和。

考虑如果一个点有兄弟,那么这两个点一定同时出现,所以你无论如何都求不出这两个点的答案。否则,我们有两种方案求出一个点的答案。

如果自己子树的 \(siz\ge k+1\) ,那么可以用父亲的子树之和减去自己的子树之和,得到的就是自己的权值。

如果自己子树的 \(siz< k+1\) ,那么他跟子树内的点一定一块出现,所以只有他是叶子的时候才有可能求出来。方法是用父亲的父亲的子树之和,减去父亲的父亲的儿子之和,减去父亲的父亲的除自己父亲外的儿子的子树之和。所以一个 \(siz< k+1\) 的点能求出来当且仅当:

1、自己是叶子

2、自己没有兄弟

3、父亲的父亲有至少 \(k\) 个直属儿子

4、父亲的父亲除了自己的父亲以外的儿子,要么是叶子,要么 \(siz\ge k+1\)

接下来我们考虑树形 \(dp\) ,令 \(f_u\) 表示以 \(u\) 为根的子树的答案,那么初始 \(f_u=\sum_{v\in son_u}f_v\) ,显然,如果一个点的 \(f>0\) ,那么这个点的 \(siz\) 一定 \(\ge k+1\)

然后,如果有一个儿子 \(p\)\(siz\ge k+1\) ,那么我们可以把其他的儿子砍掉,只剩下 \(p\) 的子树,那么 \(f_u=f_p+1\)

然后,如果这个点有至少 \(k\) 个儿子,那么我们可以把所有的 \(f=0\) 的儿子都删掉,只留下一个 \(siz\ge 2\)\(f=0\) 的儿子,然后删到只剩下一个点和一个儿子,然后剩下的点因为 \(f\ne0\) ,所以他们的 \(siz\ge k+1\) ,那么就把其他的 \(f\) 之和然后加一即可。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 800010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int head[MAXN],ednum;
struct edge{
	int nxt,to;
}ed[MAXN];
void add_Edge(int u,int v)
{
	ednum++;
	ed[ednum].nxt=head[u],ed[ednum].to=v;
	head[u]=ednum;
}
int n,K,dp[MAXN],siz[MAXN],soncnt[MAXN];
void dfs1(int u)
{
	siz[u]=1;
	FED(i,u)
	{
		int v=ed[i].to;
		dfs1(v),siz[u]+=siz[v];
	}
}
void dfs2(int u)
{
	//printf("u=%d dp=%d siz=%d\n",u,dp[u],siz[u]);
	if(siz[u]<=K) return;
	int sum=0;
	FED(i,u) dfs2(ed[i].to);
	FED(i,u) sum+=dp[ed[i].to];
	dp[u]=sum;
	FED(i,u)
	{
		int v=ed[i].to;
		if(siz[v]>=K+1) dp[u]=max(dp[u],dp[v]+1);
	}
	if(soncnt[u]>=K)
	{
		FED(i,u)
		{
			int v=ed[i].to;
			if(!dp[v]&&siz[v]>=2) dp[u]=sum+1;
		}
	}
}
void solve()
{
	FUP(i,1,n) head[i]=ed[i].nxt=ed[i].to=dp[i]=siz[i]=soncnt[i]=0;
	ednum=0;
	n=read(),K=read();
	FUP(i,2,n)
	{
		int f=read();
		add_Edge(f,i),soncnt[f]++;
	}
	dfs1(1);
	dfs2(1);
	printf("%d\n",dp[1]);
}
int main(){
	int T=read();
	while(T--) solve();
	return 0;
}

D

题意

给一个长度为 \(n\) 的数字串,定义一种划分方式的权值为每一段表示的十进制的数的积。问所有划分方式的权值之和对 \(10^9+7\) 取模的值是多少。

\(n\le 2\times 10^5\)

题解

巨大诈骗题。令 \(w(l,r)\) 表示 \([l,r]\) 这个串十进制下代表的数。然后我们可以得到一个显然的 \(dp\)\(f_i=\sum_{j=1}^{i-1}f_j\times w(j+1,i)\) ,然后我们关注 \(f_{i-1}\) 时的 \(w\)\(f_i\)\(w\) ,发现就是乘十再加上第 \(i\) 位的数字。所以你可以直接维护一个前缀和然后就线性秒了。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 200010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 998244353
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int n;
string s;
ll sum=1,re;
int main(){
	n=read();cin>>s;
	FUP(i,0,n-1)
	{
		int d=s[i]-'0';
		re=re*10%MOD;
		re=(re+sum*d%MOD)%MOD;
		sum=(sum+re)%MOD;
	}
	printf("%lld\n",re);
	return 0;
}

2021-10-13

A

题意

给一个 \(3\times 3\) 的方阵,每个位置有一个 \(1-9\) 的数,每个数恰好出现一遍。每次你可以交换相邻的两个数(上下左右),问最少经过多少次可以将其变成一个幻方,多次询问。 \(T\le 50\)

题解

考虑 \(3\times 3\) 只有 \(8\) 个幻方,可以直接多源 \(bfs\) 预处理出所有情况的答案。然后因为只有 \(9\) 个数,你可以直接把九个数压成一个 \(int\) 跑的飞快。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <unordered_map>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 100010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
const int hf[9]={0,276951438,294753618,438951276,492357816,618753294,672159834,816357492,834159672};
const int base[10]={0,100000000,10000000,1000000,100000,10000,1000,100,10,1};
unordered_map<int,int>mp;
int into(vector<int>vc)
{
	int re=0;
	for(auto p:vc) re=re*10+p;
	return re;
}
int myswap(int p,int x,int y)
{
	//printf("myswap :p=%d x=%d y=%d\n",p,x,y);
	int a=p/base[x]%10;
	int b=p/base[y]%10;
	p-=a*base[x]+b*base[y];
	p+=a*base[y]+b*base[x];
	return p;
}
int head,tail=-1;
struct node{
	int p,d;
}que[400000];
void bfs()
{
	while(tail-head+1)
	{
		int d=que[head].d,p=que[head].p;
		//printf("bfs :");printf("p=%d d=%d\n",p,d);
		head++;
		FUP(i,0,2)
		{
			FUP(j,0,1)
			{
				int np=myswap(p,i*3+j+1,i*3+j+1+1);
		//puts("111");
				if(!mp[np]) mp[np]=d+1,que[++tail]=(node){np,d+1};
			}
		}
		FUP(i,0,2)
		{
			FUP(j,0,1)
			{
				int np=myswap(p,j*3+i+1,(j+1)*3+i+1);
				if(!mp[np]) mp[np]=d+1,que[++tail]=(node){np,d+1};
			}
		}
	}
}
void work()
{
	FUP(i,1,8)
	{
		que[++tail]=(node){hf[i],1};
		mp[hf[i]]=1;
	}
	bfs();
}
void solve()
{
	vector<int>vc;
	FUP(i,0,8) vc.pb(read());
	printf("%d\n",mp[into(vc)]-1);
}
int main(){
	work();
	int T=read();
	while(T--) solve();
	return 0;
}

B

题意

说让你维护一个集合 \(S\) ,支持插入一个数,和查询一个数与集合里面的数的按位 \(xor,and,or\) 的最大值,操作数以及值域都是 \(2^{20}\)

题解

\(xor\) 的最大值足够经典,直接 \(trie\) 即可。对于 \(and\)\(or\) 是类似的,只考虑 \(and\) ,考虑从高位到低位贪心,如果目前这一位是 \(1\) ,那么他这一位就必须走 \(1\) ,否则 \(01\) 都能走,于是有个经典 \(trick\) 是说把 \(trie\) 上的 \(1\) 的子树全都复制一下 \(merge\)\(0\) 儿子上。但这个题一边插入一边查询不大好办,我们考虑另一种方式。我们发现我们走到某一位后只要求前面的必须走 \(1\) 的某几位和自己这一位是 \(1\) ,然后别的位置无所谓,所以我们插入一个数之后我们枚举他二进制的 \(1\) 的子集,然后然后对每个子集都打一下标记表示“这几位是 \(1\) ”是有可能的。然后你在打标记的时候记忆化一下,这样就可以保证打标记的时间复杂度是 \(O(V)\) 了。然后查询的时候维护一个 \(re\) ,如果这一位必须是 \(1\) ,就看看前面几位加上自己必须是一有没有可能,如果可能就把 \(re\) 这一位变成 \(1\) ,否则不操作。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN (1<<22)
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int q;
int tot=1,son[MAXN][2];
bool flg[MAXN];
//存在一个数至少在这些位置有1 
void mkflg(int x)
{
	flg[x]=true;
	FUP(i,0,19)
	{
		if(!((x>>i)&1)) continue;
		if(!flg[x^(1<<i)]) mkflg(x^(1<<i));
	}
}
void ins(int x)
{
	int u=1;
	FDW(i,19,0)
	{
		int d=(x>>i)&1;
		if(!son[u][d]) son[u][d]=++tot;
		u=son[u][d];
	}
}
int solve1(int x)
{
	int u=1,re=0;
	FDW(i,19,0)
	{
		int d=(x>>i)&1;
		if(son[u][d^1]) re^=(d^1)*(1<<i),u=son[u][d^1];
		else re^=d*(1<<i),u=son[u][d];
	}
	return re^x;
}
int solve2(int x)
{
	int re=0;
	FDW(i,19,0)
	{
		if(!((x>>i)&1)) continue;
		if(flg[re^(1<<i)]) re^=(1<<i);
	}
	return re&x;
}
int solve3(int x)
{
	int re=0;
	FDW(i,19,0)
	{
		if((x>>i)&1) continue;
		if(flg[re^(1<<i)]) re^=(1<<i);
	}
	return re|x;
}
int main(){
	q=read();
	while(q--)
	{
		int op=read(),x=read();
		if(op==1) mkflg(x),ins(x);
		if(op==2) printf("%d %d %d\n",solve1(x),solve2(x),solve3(x));
		if(op==3) printf("%d\n",solve1(x));
	}
	return 0;
}

C

题意

说给你一棵树,一开始有两个黑点,别的点都是白点,然后每个时刻每个黑点可以将相邻的一个点染成黑点(原来的黑点还是黑点),问最少需要多久可以将整棵树染黑。 \(n\le 3\times 10^5\)

题解

考虑只有一个点的时候怎么办。显然我们可以把这个点作为根,然后树形 \(dp\) 。令 \(f_u\) 表示以 \(u\) 为根的子树的答案。然后对于结点 \(u\) ,我们把他的儿子们的按 \(f\) 从大到小排好序,然后第一秒把最大的儿子染了,下一秒给第二大的儿子染了,最后统计所有儿子加上等待时间的 \(\max\) 就是他的答案了。

然后考虑两个点,我们把这棵树想成两头是这两个黑点,中间被一条链连了起来,然后这条链上面每个点上面又挂了一棵树。然后一个朴素的想法是:将这条链分成两部分,然后左边让 \(A\) 点染,右边让 \(B\) 点染,然后取左右两段的最大值作为答案,这样枚举断点是 \(O(n)\) 的。但我们发现 \(A\) 点的答案从左到右是单调不降的, \(B\) 点是不升的,那么这个 \(\max\) 最小的地方自然是 \(B\) 点第一次比 \(A\) 点小的前后变化 \(1\) 的位置,因为 \(A\) 小于 \(B\) 的可能性是具有单调性的,所以可以二分出这个位置。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <queue>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 300010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int head[MAXN],ednum;
struct edge{
	int nxt,to;
}ed[MAXN*2];
void add_Edge(int u,int v)
{
	ednum++;
	ed[ednum].nxt=head[u],ed[ednum].to=v;
	head[u]=ednum;
}
int n,A,B,sa,sb;
bool on[MAXN];
vector<int>vc;
void dfs1(int u,int fa)
{
	if(u==B) return;
	FED(i,u)
	{
		int v=ed[i].to;
		if(v==fa) continue;
		dfs1(v,u);
		if(on[v]){on[u]=true;return;}
	}
}
void dfs2(int u,int fa)
{
	vc.pb(u);
	FED(i,u)
	{
		int v=ed[i].to;
		if(v==fa||!on[v]) continue;
		dfs2(v,u);
	}
}
int dp[MAXN],ans=INF;
void dfs3(int u,int fa)
{
	dp[u]=0;
	priority_queue<int>pq;
	FED(i,u)
	{
		int v=ed[i].to;
		if(v==sa||v==fa) continue;
		dfs3(v,u);
		pq.push(dp[v]);
	}
	int cnt=0;
	while(!pq.empty())
	{
		int w=pq.top();pq.pop();
		cnt++;
		dp[u]=max(w+cnt,dp[u]);
	}
	//printf("u=%d dp=%d\n",u,dp[u]);
}
void dfs4(int u,int fa)
{
	dp[u]=0;
	priority_queue<int>pq;
	FED(i,u)
	{
		int v=ed[i].to;
		if(v==sb||v==fa) continue;
		dfs4(v,u);
		pq.push(dp[v]);
	}
	int cnt=0;
	while(!pq.empty())
	{
		int w=pq.top();pq.pop();
		cnt++;
		dp[u]=max(w+cnt,dp[u]);
	}
}
int main(){
	n=read(),A=read(),B=read(),on[A]=on[B]=true;
	FUP(i,1,n-1)
	{
		int u=read(),v=read();
		add_Edge(u,v),add_Edge(v,u);
	}
	dfs1(A,0);
	dfs2(A,0);
	int N=vc.size();
	int l=0,r=N-2;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		sa=vc[mid+1],sb=vc[mid];
		dfs3(A,0),dfs4(B,0);
		if(dp[A]<dp[B]) l=mid+1;
		else r=mid-1;
		ans=min(ans,max(dp[A],dp[B]));
	}
	printf("%d\n",ans);
	return 0;
}

D

题意

⼀个电路板可以抽象为⼀个⽆向图,图中每个点代表⼀个元件,每条边代表⼀条导线。
每个元件 \(a_i\) 有⼀个启动电压和⼀个功能属性 b_i 。当 \(a_i\le V\)\(V\) 代表整个电路的电压)时,这个元件将被激活。
激活的元件在原图中会形成若⼲个联通块(这⾥的联通块可以包含损坏的元件),对于⼀个连通块 \(A\) ,我们说它
包含功能属性 \(p\) ,当且仅当 \(A\) 中存在元件满⾜ \(b_i=p\) ,且这样的元件的个数是 \(k\) 的整数倍。
在激活的元件中,有⼀些联通块包含了损坏的元 \(V\) 和损坏的元件集合 \(S\) ,你需要回答在这个条件下,⼀个没有损
坏元件的联通块,最多包含了⼏种功能属性。

(这题意实在是太难概括了偷个懒)

题解

考虑把所有的询问离线,按 \(V\) 从小到大排序,然后这样就可以把每个点按 \(a\) 从小到大插入。然后用并查集维护连通性,然后用 \(map\) 存每个连通块每个 \(b\) 的出现次数,然后启发式合并,顺便维护数量即可,用 \(multiset\) 维护最大值。注意 \(k=1\) 的情况要特殊处理。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <set>
#include <map>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 200010
#define MAXM 1000010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int head[MAXN],ednum;
struct edge{
	int nxt,to;
}ed[MAXM];
void add_Edge(int u,int v)
{
	ednum++;
	ed[ednum].nxt=head[u],ed[ednum].to=v;
	head[u]=ednum;
}
int bcj[MAXN];
int Find(int u){return bcj[u]?bcj[u]=Find(bcj[u]):u;}
struct Query{
	int V,id,N;
	vector<int>vc;
}que[MAXN];
struct node{
	int u,a,b;
}p[MAXN];
bool cmp1(Query x,Query y){return x.V<y.V;}
bool cmp2(node x,node y){return x.a<y.a;}
int ans[MAXN];
int n,m,K,q,cnt[MAXN],a[MAXN],b[MAXN];
map<int,int>mp[MAXN];
multiset<int>S;
void myClear(map<int,int>&MP){map<int,int>__;swap(MP,__);}
bool ok[MAXN],on[MAXN];
int main(){
	//freopen("Ddata.in","r",stdin);
	n=read(),m=read(),K=read();
	FUP(i,1,n) p[i].u=i,p[i].a=a[i]=read();
	FUP(i,1,n) p[i].b=b[i]=read(),mp[i][p[i].b]++;
	if(K==1) FUP(i,1,n) cnt[i]=1;
	FUP(i,1,m)
	{
		int u=read(),v=read();
		add_Edge(u,v),add_Edge(v,u);
	}
	q=read();
	FUP(i,1,q)
	{
		que[i].V=read(),que[i].N=read(),que[i].id=i;
		FUP(j,1,que[i].N) que[i].vc.pb(read());
	}
	sort(que+1,que+q+1,cmp1),sort(p+1,p+n+1,cmp2);
	int np=1;
	//puts("111");
	FUP(qp,1,q)
	{
		//printf("qp=%d\n",qp);
		while(np<=n&&p[np].a<=que[qp].V)
		{
			int u=p[np].u;on[u]=true;
			//printf("u=%d cnt=%d\n",u,cnt[u]);
			S.insert(cnt[u]);
			//printf("np=%d u=%d\n",np,u);
			FED(i,u)
			{
				int v=ed[i].to;
				int x=Find(u),y=Find(v);
				if(x==y||!on[v]) continue;
				if(mp[x].size()<mp[y].size()) swap(x,y);
				bcj[y]=x;
				S.erase(S.find(cnt[x])),S.erase(S.find(cnt[y]));
				cnt[x]+=cnt[y],cnt[y]=0;
				for(auto ity:mp[y])
				{
					if(mp[x].count(ity.fi))
					{
						auto itx=mp[x].find(ity.fi);
						if(itx->se%K==0&&itx->se) cnt[x]--;
					}
					if(ity.se%K==0) cnt[x]--;
					if(!mp[x].count(ity.fi)) mp[x][ity.fi]=ity.se;
					else mp[x][ity.fi]+=ity.se;
					if(mp[x][ity.fi]%K==0) cnt[x]++;
				}
				//mp[y].clear();
				myClear(mp[y]);
				S.insert(cnt[x]);
				//printf("v=%d x=%d y=%d cntx=%d cnty=%d %d\n",v,x,y,cnt[x],cnt[y],mp[x][2]);
			}
			np++;
		}
			//puts("111");
		//puts("111");
		for(auto ep:que[qp].vc)
		{
			int x=Find(ep);
			//printf("x=%d ep=%d\n",x,ep);
			if(ok[x]||a[x]>que[qp].V) continue;
			ok[x]=true,S.erase(S.find(cnt[x]));
		}
		if(!S.empty()) ans[que[qp].id]=*S.rbegin();
		for(auto ep:que[qp].vc)
		{
			int x=Find(ep);
			if(!ok[x]||a[x]>que[qp].V) continue;
			ok[x]=false,S.insert(cnt[x]);
		}
	}
	FUP(i,1,q) printf("%d\n",ans[i]);
	return 0;
}

2021-10-14

A

题意

一张 \(n\) 个点 \(m\) 条边的无向图,问有多少种点的黑白染色方案使得对于所有 \(k\in[0,m]\) ,恰好有 \(k\) 条边的两端颜色都是黑色,没有重边和自环。 \(n\le 25,m\le \frac{n\times(n-1)}{2}\)

题解

给每条边定向,从编号小的指向编号大的,从小到大枚举每个点染成什么颜色,顺便维护符合要求的边的数量,如果自己染成了黑色,那么就看前面有几个点指向自己而且在目前枚举的染色方案里被染成了黑色,压位直接按位与即可,时间复杂度 \(O(n)\)

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <bitset>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 100010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int n,m,re[500];
int b[26],tmp;
void dfs(int cur,int sum)
{
	//cout<<(bitset<5>)tmp<<" ";
	//printf("cur=%d sum=%d\n",cur,sum);
	if(cur==n) re[sum]++;
	else
	{
		cur++,tmp^=1<<cur;
		dfs(cur,sum+__builtin_popcount(tmp&b[cur]));
		tmp^=1<<cur;
		dfs(cur,sum);
	}
}
int main(){
	//freopen("ex_A2.in","r",stdin);
	//freopen("my.out","w",stdout);
	n=read(),m=read();
	FUP(i,1,m)
	{
		int u=read(),v=read();
		if(u>v) swap(u,v);
		b[v]^=1<<u;
	}
	dfs(0,0);
	FUP(i,0,m) printf("%d ",re[i]);
	return 0;
}

B

题意

给一个长度为 \(n\) 个序列 \(a\)\(q\) 次询问一个区间内有多少个数与给定的数 \(x\) 互质,所有数字都是 \(10^5\)

题解

考场上直接想的容斥,后来发现和莫反等价,就直接写莫反了。

\(\sum_{i=l}^r[\gcd(x,a_i)=1]=\sum_{i=l}^r\sum_{d|\gcd(x,a_i)}\mu(d)\) ,这样直接对 \(x\) 和每个 \(a\) 质因数分解,然后扔 \(vector\) 里,每次直接在 \(vector\) 里二分就好了。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <vector>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 100010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int prm[MAXN],prnum,mu[MAXN];
bool isprime[MAXN];
vector<int>vc[MAXN],pos[MAXN];
void sieve()
{
	FUP(i,2,1e5)
	{
		if(!isprime[i]) vc[i].pb(i),mu[i]=-1,prm[++prnum]=i;
		FUP(j,1,prnum)
		{
			int w=i*prm[j];
			if(w>1e5) break;
			isprime[w]=true;
			/*if(w==17459)
			{
				printf("i=%d\n",i);
				for(auto p:vc[i]) printf("%d ",p);puts("");
			}*/
			for(auto p:vc[i]) vc[w].pb(p);
			if(i%prm[j])
			{
				vc[w].pb(prm[j]);
				/*if(w==17459)
				{
					for(auto p:vc[w]) printf("%d ",p);puts("");
				}*/
				for(auto p:vc[i])
				{
					vc[w].pb(prm[j]*p);
				}
				mu[w]=-mu[i];
			}
			else break;
		}
	}
	/*FUP(i,17459,17459)
	{
		printf("i=%d isprime=%d mu=%d fj=",i,isprime[i],mu[i]);
		for(auto p:vc[i]) printf("%d ",p);
		puts("");
	}*/
}
int n,m,a[MAXN];
int main(){
	//freopen("ex_B2.in","r",stdin);
	//freopen("my.out","w",stdout);
	sieve();
	n=read(),m=read();
	FUP(i,1,n)
	{
		a[i]=read();
		for(auto p:vc[a[i]]) pos[p].pb(i);
	}
	FUP(i,1,1e5) pos[i].pb(n+1);
	/*FUP(i,1,7)
	{
		printf("i=%d :",i);
		for(auto id:pos[i]) printf("%d ",id);
		puts("");
	}*/
	FUP(i,1,m)
	{
		int ql=read(),qr=read(),x=read(),tot=qr-ql+1;
		for(auto p:vc[x])
		{
			int fn=upper_bound(pos[p].begin(),pos[p].end(),qr)-pos[p].begin()-1;
			int bg=lower_bound(pos[p].begin(),pos[p].end(),ql)-pos[p].begin();
			int cnt=fn-bg+1;
			//printf("p=%d mu=%d ql=%d qr=%d bg=%d fn=%d cnt=%d %d %d\n",p,mu[p],ql,qr,bg,fn,cnt,pos[p][bg],pos[p].size());
			if(pos[p][bg]>qr) continue; 
			tot+=cnt*mu[p];
		}
		printf("%d\n",tot);
	}
	return 0;
}

C

题意

\(n\) 个人,告诉你两两比赛谁会获胜,每次可以选择相邻的两个人比赛,最后对于每个人问他有没有可能赢。 \(n\le 2000\)

题解

发现每个点把左右两个区间隔开了,那么只要左边右边都可以剩下一个他可以打败的那么他就可以,于是我们发现我们只需要记录一个区间的边界在这个区间内部的胜负即可。定义 \(f_{i,j}\) 表示在由 \(i,j\) 组成的区间里( \(i\) 有可能比 \(j\) 大), \(i\) 有没有可能获胜。这样我们进行区间 \(dp\) ,然后以 \(i< j\) 的情况为例:只要存在一个位置 \(p\in[i+1,j],f_{p,i+1}=1,f_{p,j}=1\) 并且 \(i\) 可以打败 \(p\) 即可。那么我们把这四个限制直接按位与起来,看看有没有位置是 \(1\) 即可,用 \(bitset\) 优化转移。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <bitset>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 2010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
int n;
bitset<MAXN>a[MAXN],dp[MAXN];

int main(){
	//freopen("ex_C2.in","r",stdin);
	n=read();
	FUP(i,1,n)
	{
		FUP(j,1,n) a[i][j]=getchar()-'0';
		getchar();
	}
	FUP(i,1,n)
	FUP(i,1,n) dp[i][i]=1;
	FUP(len,2,n)
	{
		bitset<MAXN>tmp;
		FUP(j,1,len) tmp[j]=1;
		for(int l=1,r=l+len-1;r<=n;l++,r++)
		{
			if((dp[l]&dp[r-1]&a[r]&tmp).any()) dp[l][r]=1;
			if((dp[r]&dp[l+1]&a[l]&tmp).any()) dp[r][l]=1;
			tmp[l]=0,tmp[r+1]=1;
		}
	}
	FUP(i,1,n) if(dp[1][i]&&dp[n][i]) printf("%d ",i);
	return 0;
}

D

题意

说给你一张带权无向图,有一条边被断掉了,然后你只有走到这条边两个端点时你才能知道这条边断了。然后你要从一个点去 \(T\) ,问最坏情况下,你按照最优策略走,路程上的最小权值之和是多少,对于每个起始点求出答案,如果有可能走不到输出 \(-1\)\(n\le 10^6,m\le 2\times 10^6\) ,时限 \(7s\)

题解

可以把这个题想成一个博弈的模型,你一直走,然后对手会决定现在要不要在你身边堵一条边。

然后考虑原图的最短路树,对手必然会堵这棵最短路树上的边,否则你顺着路径走到根就好了。然后你可能不会沿着最短路走,因为最坏情况下你需要绕出来付出很大的代价。所以定义 \(f_u\) 表示 \(u\) 的答案,定义 \(val_u\) 表示不走 \(u\rightarrow fa_u\) 这条边的最短路,那么 \(f_u=\max(val_u,\min_{(u,v)\in E}) f_v+e(u,v)\) ,这个式子的意思是堵了你到父亲的边和不堵的最大值(因为是最坏情况嘛),然后不堵的话你要从所有出点的最坏情况下选择一个最好的。然后这个式子等价于 \(f_u=\min_{(u,v)\in E}\max(val_u,f_v+e(u,v))\) ,所以你可以直接套个 \(dij\) 出来。

接下来问题变成了怎么求 \(val_u\) ,显然每个点最多之后走一条非树边,假设 \(u\) 走了 \((x,y,w)\) 这条非树边,满足 \(u\)\(x\)\(y\) 的路径上并且不是 \(lca\) ,那么 \(val_u=dis_x+dis_y+w-dis_u\) ,所以可以直接按照这个值把所有的非树边从小到大排序,然后对路径上所有没被赋值的点附上值,用树上并查集即可。

\(code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <vector>
#define FUP(i,x,y) for(int i=(x);i<=(y);i++)
#define FDW(i,x,y) for(int i=(x);i>=(y);i--)
#define FED(i,x) for(int i=head[x];i;i=ed[i].nxt)
#define pr pair<int,int>
#define mkp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pb(x) push_back(x)
#define MAXN 1000010
#define MAXM 2000010
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define eps 1e-9
#define MOD 1000000007
#define ll long long
#define db double
using namespace std;
int read()
{
	int w=0,flg=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-'){flg=-1;}ch=getchar();}
	while(ch<='9'&&ch>='0'){w=w*10+ch-'0',ch=getchar();}
	return w*flg;
}
const bool is_print=0;
int head[MAXN],ednum;
struct edge{
	int nxt,to,w;
}ed[MAXM*2];
void add_Edge(int u,int v,int w)
{
	ednum++;
	ed[ednum].nxt=head[u],ed[ednum].to=v,ed[ednum].w=w;
	head[u]=ednum;
}
int n,m,T;
struct node{
	int id;ll d;
};
bool operator<(node x,node y){return x.d>y.d;}
ll dis[MAXN],val[MAXN];
bool vis1[MAXN];
int fa[MAXN],lst[MAXN],p[MAXN];
bool cmp1(int x,int y){return dis[x]<dis[y];}
priority_queue<node>pq1;
void dij1()
{
	memset(dis,0x3f,sizeof(dis));
	dis[T]=0,pq1.push((node){T,0});
	while(!pq1.empty())
	{
		int u=pq1.top().id;pq1.pop();
		if(vis1[u]) continue;
		vis1[u]=true;
		FED(i,u)
		{
			int v=ed[i].to;
			if(dis[v]>dis[u]+ed[i].w) 
			{
				dis[v]=dis[u]+ed[i].w,fa[v]=u,lst[v]=i;
				pq1.push((node){v,dis[v]});
			}
		}
	}
}
int bcj[MAXN],dep[MAXN];
int Find(int u){return bcj[u]?bcj[u]=Find(bcj[u]):u;}
struct E{
	int u,v;ll w;
};
bool cmp2(E x,E y){return x.w<y.w;}
vector<E>vc;
void solve(int u,int v,ll w)
{
	u=Find(u),v=Find(v);
	if(dis[u]==INF||dis[v]==INF) return;
		if(is_print) printf("solve :u=%d v=%d w=%lld %d %d\n",u,v,w,fa[u],fa[v]);
	while(u!=v)
	{
		if(dep[u]<dep[v]) swap(u,v);
		val[u]=w;
		bcj[u]=fa[u],u=Find(u);
	}
}
ll ans[MAXN];
bool vis2[MAXN];
priority_queue<node>pq2;
void dij2()
{
	memset(ans,0x3f,sizeof(ans));
	pq2.push((node){T,0}),val[T]=ans[T]=0;
	while(!pq2.empty())
	{
		int u=pq2.top().id;pq2.pop();
		if(vis2[u]) continue;
		vis2[u]=true;
		FED(i,u)
		{
			int v=ed[i].to;
			ll w=max(ans[u]+ed[i].w,val[v]);
			if(is_print) printf("%d->%d %lld\n",u,v,w);
			if(is_print) puts("");
			if(ans[v]>w)
			{
				ans[v]=w;
				pq2.push((node){v,ans[v]});
			}
		}
	}
}
int main(){
	//freopen("Ddata.in","r",stdin);
	//freopen("Dmy.out","w",stdout);
	n=read(),m=read(),T=read();
	FUP(i,1,m)
	{
		int u=read(),v=read(),w=read();
		add_Edge(u,v,w),add_Edge(v,u,w);
	}
	dij1();
	if(is_print) FUP(i,1,n) printf("%lld ",dis[i]);
	if(is_print) puts("");
	FUP(u,1,n)
	{
		p[u]=u;
		FED(i,u)
		{
			int v=ed[i].to;
			if(i!=lst[v]&&dis[u]<dis[v]) vc.push_back((E){u,v,dis[u]+dis[v]+ed[i].w});
		}
	}
	sort(p+1,p+n+1,cmp1),sort(vc.begin(),vc.end(),cmp2);
	FUP(i,2,n) dep[p[i]]=dep[fa[p[i]]]+1;
	FUP(i,1,n) val[i]=dis[i]+LLINF;
	for(auto p:vc) solve(p.u,p.v,p.w);
	FUP(i,1,n) val[i]-=dis[i];
	if(is_print) FUP(i,1,n) printf("%lld ",val[i]);
	if(is_print) puts("");
	dij2();
	FUP(i,1,n) printf("%lld ",ans[i]>=LLINF?-1:ans[i]);
	return 0;
}


posted @ 2021-10-12 17:14  Pbri  阅读(214)  评论(0编辑  收藏  举报