2020牛客暑期多校训练营(第二场)题解

废话

蒟蒻不会积分,K不会做。

A. All with Pairs

给出 n n n 个字符串,求出两两间的最长前后缀长度的平方之和。

看到 n n n 个字符串之间的信息处理,容易想到先建一个广义SAM。

考虑将每个字符串的结尾在SAM上打个标记,那么在后缀树中每个状态的子树内的标记数就代表这个状态是多少个字符串的后缀

那么对于每个字符串,再将它丢到SAM里面跑一遍,统计出每个前缀的答案即可。

但是要注意去重,先求出每个字符串的next数组,可以注意到,假如一个前缀是 k k k 个字符串的后缀,那么这个前缀的next对应的前缀也一定是这 k k k 个字符串的一个后缀,于是需要去掉next的贡献。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 2000010
#define mod 998244353

int n;
vector<char>s[maxn];
char ss[maxn];
struct state{
    
    int len,link,next[26];}st[maxn];
int id=0,last,now,p,q,size[maxn];
void extend(int x)
{
    
    
	if(st[last].next[x])
	{
    
    
		p=last;q=st[p].next[x];
		if(st[p].len+1==st[q].len)last=q;
		else
		{
    
    
			int clone=++id;
			st[clone]=st[q];st[clone].len=st[p].len+1;
			for(;p!=-1&&st[p].next[x]==q;p=st[p].link)st[p].next[x]=clone;
			st[q].link=clone; last=clone;
		}
		return;
	}
	now=++id;st[now].len=st[last].len+1;
	for(p=last;p!=-1&&!st[p].next[x];p=st[p].link)st[p].next[x]=now;
	if(p!=-1)
	{
    
    
		q=st[p].next[x];
		if(st[p].len+1==st[q].len)st[now].link=q;
		else
		{
    
    
			int clone=++id;
			st[clone]=st[q];st[clone].len=st[p].len+1;
			for(;p!=-1&&st[p].next[x]==q;p=st[p].link)st[p].next[x]=clone;
			st[q].link=st[now].link=clone;
		}
	}
	last=now;
}
int c[maxn],A[maxn];
void get_size()
{
    
    
	for(int i=1;i<=id;i++)c[st[i].len]++;
	for(int i=1;i<=id;i++)c[i]+=c[i-1];
	for(int i=1;i<=id;i++)A[c[st[i].len]--]=i;
	for(int i=id;i>=1;i--)size[st[A[i]].link]+=size[A[i]];
}
vector<int>Next[maxn];
void get_next(int x)
{
    
    
	Next[x].push_back(-1);
	for(int i=1;i<s[x].size();i++){
    
    
		int j=Next[x][i-1];
		while(j!=-1&&s[x][j+1]!=s[x][i])j=Next[x][j];
		if(s[x][j+1]==s[x][i])j++;
		Next[x].push_back(j);
	}
}
int ans=0;
void add(int &x,int y){
    
    x=(x+y>=mod?x+y-mod:x+y);}
void dec(int &x,int y){
    
    x=(x-y<0?x-y+mod:x-y);}

int main()
{
    
    
	scanf("%d",&n);st[0].link=-1;
	for(int i=1,L;i<=n;i++){
    
    
		scanf("%s",ss);L=strlen(ss);last=0;
		for(int j=0;j<L;j++)s[i].push_back(ss[j]),extend(ss[j]-'a');
		size[last]++;
	}
	get_size();
	for(int i=1;i<=n;i++){
    
    
		get_next(i);int now=0;
		for(int j=0;j<s[i].size();j++){
    
    
			now=st[now].next[s[i][j]-'a'];
			add(ans,1ll*size[now]*(j+1)%mod*(j+1)%mod);
			dec(ans,1ll*size[now]*(Next[i][j]+1)%mod*(Next[i][j]+1)%mod);
		}
	}
	printf("%d",ans);
}

B. Boundary

给出 n n n 个点,找到一个过原点的圆,使得有尽可能多的点在该圆上。

因为三点确定一个圆,而其中一个点一定是原点,于是可以枚举剩下两个点是谁,然后就可以得到 n 2 n^2 n2 个圆,判断一下其中有多少个相同的圆,就能算出这个相同圆上有多少个点。

而三点确定一个圆的方法这里顺手记录一下吧,毕竟以前没写过qwq。

设三点坐标为 ( x 1 , y 1 ) , ( x 2 , y 2 ) , ( x 3 , y 3 ) (x_1,y_1),(x_2,y_2),(x_3,y_3) (x1,y1),(x2,y2),(x3,y3),原点坐标为 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),那么有:
{ ( x 1 − x 0 ) 2 + ( y 1 − y 0 ) 2 = r 2 ( x 2 − x 0 ) 2 + ( y 2 − y 0 ) 2 = r 2 ( x 3 − x 0 ) 2 + ( y 3 − y 0 ) 2 = r 2 \begin{cases} (x_1-x_0)^2+(y_1-y_0)^2=r^2\\ (x_2-x_0)^2+(y_2-y_0)^2=r^2\\ (x_3-x_0)^2+(y_3-y_0)^2=r^2 \end{cases} (x1x0)2+(y1y0)2=r2(x2x0)2+(y2y0)2=r2(x3x0)2+(y3y0)2=r2

相邻柿子相减,能得到三条类似的柿子,这里只用其中两个:
x 1 2 − 2 x 1 x 0 + y 1 2 − 2 y 1 y 0 − x 2 2 + 2 x 2 x 0 − y 2 2 + 2 y 2 y 0 = 0 x 1 2 − 2 x 1 x 0 + y 1 2 − 2 y 1 y 0 − x 3 2 + 2 x 3 x 0 − y 3 2 + 2 y 3 y 0 = 0 x_1^2-2x_1x_0+y_1^2-2y_1y_0-x_2^2+2x_2x_0-y_2^2+2y_2y_0=0\\ x_1^2-2x_1x_0+y_1^2-2y_1y_0-x_3^2+2x_3x_0-y_3^2+2y_3y_0=0 x122x1x0+y122y1y0x22+2x2x0y22+2y2y0=0x122x1x0+y122y1y0x32+2x3x0y32+2y3y0=0

整理得到:
2 ( x 2 − x 1 ) x 0 + 2 ( y 2 − y 1 ) y 0 = x 2 2 + y 2 2 − x 1 2 − y 1 2 2 ( x 3 − x 1 ) x 0 + 2 ( y 3 − y 1 ) y 0 = x 3 2 + y 3 2 − x 1 2 − y 1 2 2(x_2-x_1)x_0+2(y_2-y_1)y_0=x_2^2+y_2^2-x_1^2-y_1^2\\ 2(x_3-x_1)x_0+2(y_3-y_1)y_0=x_3^2+y_3^2-x_1^2-y_1^2 2(x2x1)x0+2(y2y1)y0=x22+y22x12y122(x3x1)x0+2(y3y1)y0=x32+y32x12y12

那么我们就得到了一个关于 x 0 , y 0 x_0,y_0 x0,y0 的二元一次方程组,随便解解就能得到 x 0 , y 0 x_0,y_0 x0,y0 了。

这题不需要求 r r r,但是要求也是很简单的,将 x 0 , y 0 x_0,y_0 x0,y0 带入一开始的三条柿子中的随便一条即可。

代码如下:

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 2010
#define db double

int n,t=0;
struct point{
    
    db x,y;}s[maxn],d[maxn*maxn];
void solve(point a,point b,point c)
{
    
    
	db down1=2*(b.x-a.x)*(c.y-a.y)-2*(c.x-a.x)*(b.y-a.y);
	db down2=2*(b.y-a.y)*(c.x-a.x)-2*(b.x-a.x)*(c.y-a.y);
	if(!down1||!down2)return;
	db up1=b.x*b.x+b.y*b.y-a.x*a.x-a.y*a.y;
	db up2=c.x*c.x+c.y*c.y-a.x*a.x-a.y*a.y;
	db x_0=(up1*(c.y-a.y)-up2*(b.y-a.y))/down1;
	db y_0=(up1*(c.x-a.x)-up2*(b.x-a.x))/down2;
	d[t++]=(point){
    
    x_0,y_0};
}
bool cmp(point x,point y){
    
    return x.x==y.x?(x.y<y.y):x.x<y.x;}

int main()
{
    
    
	scanf("%d",&n);if(n==1)return printf("1"),0;
	for(int i=1;i<=n;i++)scanf("%lf %lf",&s[i].x,&s[i].y);
	for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)solve((point){
    
    0,0},s[i],s[j]);
	if(!t)return printf("1"),0;
	sort(d,d+t,cmp);
	
	int ans=1,tot=1;
	for(int i=1;i<t;i++)
	if(d[i].x==d[i-1].x&&d[i].y==d[i-1].y)tot++;
	else {
    
    if(tot>ans)ans=tot;tot=1;}
	if(tot>ans)ans=tot;
	for(int i=1;i<=n;i++)if(i*(i-1)/2==ans)return printf("%d",i),0;
}

C. Cover the Tree

在树上找最少的链覆盖所有边。

设叶子节点数为 t t t,容易知道链数的一个下界是 ⌈ t 2 ⌉ \lceil \dfrac t 2 \rceil 2t

先选定一个度大于 1 1 1 的点作为根,然后将所有叶子节点按dfs序排序得到 a a a 数组,那么选出的链就是 a i a_i ai a i + ⌊ t 2 ⌋ a_{i+\lfloor \frac t 2 \rfloor} ai+2t,假如 t t t 是奇数,那么让 a t a_t at 再向根连一条链即可。

证明的话考虑每条父子边,假如点 x x x 到父亲之间的边没有被覆盖过,那么说明 x x x 的子树内的所有叶子节点都相互配对了,没有和子树外的叶子节点配对的情况,而显然这种情况只可能在根节点上发生,而根节点没有到父亲之间的边。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 200010

int n,du[maxn];
struct edge{
    
    int y,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y){
    
    e[++len]=(edge){
    
    y,first[x]};first[x]=len;}
int ans[maxn],t=0;
void dfs(int x,int fa)
{
    
    
	if(du[x]==1)ans[++t]=x;
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa)dfs(e[i].y,x);
}

int main()
{
    
    
	scanf("%d",&n);for(int i=1,x,y;i<n;i++)
	scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x),du[x]++,du[y]++;
	if(n==2)return printf("1\n1 2"),0;
	int root;for(int i=1;i<=n;i++)if(du[i]>1){
    
    root=i;break;}
	dfs(root,0);
	printf("%d\n",(t+1)/2);
	for(int i=1;i<=t/2;i++)printf("%d %d\n",ans[i],ans[i+t/2]);
	if(t%2)printf("%d %d",root,ans[t]);
}

D. Duration

给你同一天内的两个时刻,输出时间差。

水题qwq。

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;

int a,b,c,e,f,g,ans=0;

int main()
{
    
    
	scanf("%d:%d:%d",&a,&b,&c);
	scanf("%d:%d:%d",&e,&f,&g);
	if(a<e||(a==e&&b<f)||(a==e&&b==f&&c<g))swap(a,e),swap(b,f),swap(c,g);
	if(g>c)c+=60,b--;ans+=c-g;
	if(f>b)b+=60,a--;ans+=(b-f)*60;
	ans+=(a-e)*3600;printf("%d",ans);
}

E. Exclusive OR

给出 n n n 个数,问用其中 k k k 个数异或起来能得到的最大数是什么, k ∈ [ 1 , n ] k\in[1,n] k[1,n]

容易发现,当 i > 19 i>19 i>19 的时候,满足 a n s i = a n s i − 2 ans_i=ans_{i-2} ansi=ansi2,因为 a i ≤ 2 18 a_i\leq 2^{18} ai218,也就是说这个序列的线性基最多 18 18 18 个数,即 18 18 18 个数以内可以凑出这 n n n 个数能异或出的最大值,而 a n s i − 2 ans_{i-2} ansi2 再随便异或两个相同的数就可以得到 a n s i ans_i ansi

但是有可能取奇数个数不能凑出这个最大值,所以需要求第 19 19 19 个数能凑出的最大值,所以对于 a n s 1 ans_1 ans1 ~ a n s 19 ans_{19} ans19,需要用 F W T FWT FWT 来求,后面的直接用 a n s i = a n s i − 2 ans_i=ans_{i-2} ansi=ansi2 推即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn (1<<19)
#define mod 998244353

int n,a[maxn],b[maxn],ans[maxn];
const int inv2=(mod+1)/2;
void FWT(int *f,int type=0,int len=(1<<18))
{
    
    
	for(int mid=1;mid<len;mid<<=1)
	for(int j=0;j<len;j+=(mid<<1))
	for(int i=0;i<mid;i++){
    
    
		int x=f[j+i],y=f[j+i+mid];
		f[j+i]=1ll*(x+y)*(type==1?inv2:1)%mod;
		f[j+i+mid]=1ll*(x-y+mod)*(type==1?inv2:1)%mod;
	}
}

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1,x;i<=n;i++){
    
    
		scanf("%d",&x);
		a[x]=1,b[x]=1;
		ans[1]=max(ans[1],x);
	}
	FWT(b);for(int i=2;i<=19;i++){
    
    
		FWT(a);for(int j=0;j<(1<<18);j++)a[j]=1ll*a[j]*b[j]%mod;
		FWT(a,1);for(int j=0;j<(1<<18);j++)if(a[j])ans[i]=j,a[j]=1;
	}
	for(int i=19;i<=n;i++)ans[i]=ans[i-2];
	for(int i=1;i<=n;i++)printf("%d ",ans[i]);
}

F .Fake Maxpooling

给出一个 n × m n\times m n×m 的矩阵,第 i i i j j j 列的数为 l c m ( i , j ) lcm(i,j) lcm(i,j),求出每个 k × k k\times k k×k 的子矩阵中最大值的和。

优先队列比较裸的应用了,先横着跑一遍再竖着跑一遍即可。

代码如下:

#include <cstdio>
#define maxn 5010

int n,m,k,a[maxn][maxn],ma[maxn][maxn];
int gcd(int x,int y){
    
    return !y?x:gcd(y,x%y);}
struct par{
    
    int x,y;}q[maxn];
int st,ed;long long ans=0;

int main()
{
    
    
	scanf("%d %d %d",&n,&m,&k);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)a[i][j]=i*j/gcd(i,j);
	for(int i=1;i<=n;i++){
    
    
		st=1;ed=0;
		for(int j=1;j<k;j++){
    
    
			while(st<=ed&&a[i][j]>q[ed].x)ed--;
			q[++ed]=(par){
    
    a[i][j],j};
		}
		for(int j=k;j<=m;j++){
    
    
			while(st<=ed&&q[st].y<j-k+1)st++;
			while(st<=ed&&a[i][j]>q[ed].x)ed--;
			q[++ed]=(par){
    
    a[i][j],j};
			ma[i][j]=q[st].x;
		}
	}
	for(int j=k;j<=m;j++){
    
    
		st=1;ed=0;
		for(int i=1;i<k;i++){
    
    
			while(st<=ed&&ma[i][j]>q[ed].x)ed--;
			q[++ed]=(par){
    
    ma[i][j],i};
		}
		for(int i=k;i<=n;i++){
    
    
			while(st<=ed&&q[st].y<i-k+1)st++;
			while(st<=ed&&ma[i][j]>q[ed].x)ed--;
			q[++ed]=(par){
    
    ma[i][j],i};
			ans+=q[st].x;
		}
	}
	printf("%lld\n",ans);
}

G. Greater and Greater

A A A 中有多少个长度为 m m m 的区间,满足区间内每一个数都不小于 B B B 中对应的数。

先求出 n n n 个bitset,当 A i ≥ B j A_i\geq B_j AiBj 时, s i [ j ] s_i[j] si[j] 1 1 1,可以发现只有 m m m 个本质不同的 s i s_i si,因为当 A i A_i Ai 变大时,比它小的数依然比它小,而原来比他大的数有可能会变得比它小。

再设 n n n 个bitset, c u r i [ j ] cur_i[j] curi[j] 1 1 1 时当且仅当满足 ∀ k ∈ [ 1 , j ] , A i − j + k ≥ B k \forall k\in[1,j],A_{i-j+k}\geq B_k k[1,j],Aij+kBk,那么答案就是有多少个 c u r i [ m ] cur_i[m] curi[m] 1 1 1

可以发现 c u r cur cur 有这样的转移: c u r i = ( ( c u r i − 1 < < 1 ) ∣ 1 ) & s i cur_i=((cur_{i-1}<<1)|1)\&s_i curi=((curi1<<1)1)&si,于是就做完了。

代码如下:

#include <cstdio>
#include <bitset>
#include <algorithm>
using namespace std;
#define maxn 150010
#define maxm 40010

int n,m,a[maxn],b[maxm],pos[maxm],ans;
bitset<maxm>s[maxm],now,one;
bool cmp(int x,int y){
    
    return b[x]<b[y];}

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=m;i++)scanf("%d",&b[i]),pos[i]=i;
	sort(pos+1,pos+m+1,cmp);
	sort(b+1,b+m+1);
	for(int i=1;i<=m;i++)s[i]=s[i-1],s[i][pos[i]]=1;
	one[1]=1;
	for(int i=1;i<=n;i++){
    
    
		int d=upper_bound(b+1,b+m+1,a[i])-b-1;
		now=((now<<1)|one)&s[d];
		ans+=now[m];
	}
	printf("%d",ans);
}

H. Happy Triangle

q q q 次操作,有三种操作:1、加入一个数;2、删除一个数;3、询问能否用当前拥有的数与 x x x 组成三角形。

询问能否与 x x x 组成三角形,相当于问是否存在两个数,差小于 x x x 且和大于 x x x

显然我们只需要看排序后所有相邻的两个数即可,那么用一颗 s p l a y splay splay 维护所有相邻两个数的差与和即可,以差为第一关键字,和为第二关键字,每次询问相当于在差小于 x x x 的节点里面找最大的和,判断这个和是否大于 x x x

蒟蒻的实现很不优美……代码如下:

#include <cstdio>
#include <set>
#include <map>
#include <algorithm>
using namespace std;
#define it set<int>::iterator

int n;
set<int>a;
map<int,int>A;
struct par{
    
    
	int key,val;par(int x=0,int y=0):key(x),val(y){
    
    }
	bool operator <(const par &B){
    
    return key<B.key||(key==B.key&&val<B.val);}
	bool operator >(const par &B){
    
    return key>B.key||(key==B.key&&val>B.val);}
	bool operator ==(const par &B){
    
    return key==B.key&&val==B.val;}
};
struct node{
    
    
	int ma,tot;par z;node *zuo,*you,*fa;
	node(int x,int y,node *Fa):z(x,y),ma(y),tot(1),zuo(NULL),you(NULL),fa(Fa){
    
    }
	node *find(par x)
	{
    
    
		if(zuo&&x<z)return zuo->find(x);
		if(you&&x>z)return you->find(x);
		return this;
	}
	void check()
	{
    
    
		ma=z.val;
		if(zuo)ma=max(ma,zuo->ma);
		if(you)ma=max(ma,you->ma);
	}
}*root=NULL;
void rotate(node *x)
{
    
    
	node *fa=x->fa,*gfa=fa->fa;
	if(fa->zuo==x){
    
    
		fa->zuo=x->you;
		if(x->you)x->you->fa=fa;
		x->you=fa;
	}else{
    
    
		fa->you=x->zuo;
		if(x->zuo)x->zuo->fa=fa;
		x->zuo=fa;
	}
	fa->fa=x;x->fa=gfa;
	if(gfa&&gfa->zuo==fa)gfa->zuo=x;
	if(gfa&&gfa->you==fa)gfa->you=x;
	fa->check();x->check();
}
#define witch(x) (x->fa->zuo==x)
void splay(node *x,node *to=NULL)
{
    
    
	while(x->fa!=to){
    
    
		if(x->fa->fa!=to&&witch(x)==witch(x->fa))rotate(x->fa),rotate(x);
		else rotate(x);
	}
	if(!to)root=x;
}
void add(par x)
{
    
    
	if(root==NULL)root=new node(x.key,x.val,NULL);
	else
	{
    
    
		node *p=root->find(x);
		if(p->z==x)p->tot++;
		else if(x<p->z)p->zuo=new node(x.key,x.val,p),splay(p->zuo);
		else p->you=new node(x.key,x.val,p),splay(p->you);
	}
}
void del(par x)
{
    
    
	node *p=root->find(x);splay(p);
	if(p->tot>1){
    
    p->tot--;return;}
	if(!root->zuo&&!root->you)root=NULL;
	else if(!root->zuo&&root->you)root=root->you,root->fa=NULL;
	else if(root->zuo&&!root->you)root=root->zuo,root->fa=NULL;
	else{
    
    
		p=root->zuo;
		while(p->you)p=p->you;splay(p,root);
		if(root->you)root->zuo->you=root->you,root->you->fa=root->zuo;
		root=root->zuo;root->fa=NULL;
	}
}
node *next(node *x){
    
    splay(x);x=x->you;while(x->zuo)x=x->zuo;return x;}

int main()
{
    
    
	scanf("%d",&n);
	add(par(-1,0));add(par(2147483647,0));
	for(int i=1,op,x;i<=n;i++)
	{
    
    
		scanf("%d %d",&op,&x);
		switch(op)
		{
    
    
			case 1:
				A[x]++;if(A[x]==1){
    
    
					if(!a.size())a.insert(x);
					else{
    
    
						it pre=a.upper_bound(x),next=pre;bool v1=false,v2=false;
						if(pre!=a.begin())pre--,add(par(x-*pre,x+*pre)),v1=true;
						if(next!=a.end())add(par(*next-x,*next+x)),v2=true;
						if(v1&&v2)del(par(*next-*pre,*next+*pre));
						a.insert(x);
					}
				}else add(par(0,x*2));
				break;
			case 2:
				A[x]--;if(!A[x]){
    
    
					if(a.size()==1)a.clear();
					else{
    
    
						a.erase(x);
						it pre=a.upper_bound(x),next=pre;bool v1=false,v2=false;
						if(pre!=a.begin())pre--,del(par(x-*pre,x+*pre)),v1=true;
						if(next!=a.end())del(par(*next-x,*next+x)),v2=true;
						if(v1&&v2)add(par(*next-*pre,*next+*pre));
					}
				}else del(par(0,x*2));
				break;
			case 3:
				node *p=root->find(par(x,0));
				if(p->z.key<x)p=next(p);splay(p);
				if(p->zuo->ma>x)printf("Yes\n");
				else printf("No\n");
				break;
		}
	}
}

I. Interval

一开始有一个区间 [ 1 , n ] [1,n] [1,n],你可以将区间扩大或缩小,有 m m m 个限制,每个可以将一个变化方式限制掉,如限制 [ l , r ] [l,r] [l,r] [ l + 1 , r ] [l+1,r] [l+1,r] 不能相互转化,每一个限制有个代价,现在要最小代价的情况下使 [ 1 , n ] [1,n] [1,n] 无法缩成任意 [ i , i ] [i,i] [i,i]

可以将区间看成坐标,那么这就是一个网格图,缩小和扩张对应上下左右四个方向的移动,现在相当于ban掉若干条边不能走,然后使 ( 1 , n ) (1,n) (1,n) 不能到达 ( i , i ) (i,i) (i,i)

狼抓兔子那题有点不一样,那题的边是单向的,可以通过求最大流得到最小割从而得到答案,而这题的边是双向的,也就是两条单向边,但事实上ban掉这两条边只需要一次代价,所以没法用网络流,只能转成对偶图来求最短路。

具体来说,我们要求的是 ( 1 , n ) (1,n) (1,n) 到所有 ( i , i ) (i,i) (i,i) 的一个最小割,先将所有 ( i , i ) (i,i) (i,i) 连向 T T T(超级汇点),然后再连一条 S S S T T T 的边,像这样:
在这里插入图片描述

然后转化成对偶图,即将每个面看成点,假如两个面有公共边就连一条边,那么可以发现,面 s s s 到面 t t t 的一条路径其实就对应原图的一个割,那么最短路就是最小割。

代码如下:

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
#define maxn 300010
#define maxm 510
#define ll long long
#define inf 999999999999ll

int n,m,S,T;
ll down[maxm][maxm],left[maxm][maxm];
struct edge{
    
    int y,next;ll z;}e[maxn<<2];
int first[maxn],len=0;
void buildroad(int x,int y,ll z){
    
    e[++len]=(edge){
    
    y,first[x],z};first[x]=len;}
void ins(int x,int y,ll z){
    
    buildroad(x,y,z);buildroad(y,x,z);}
int id(int x,int y){
    
    return (x-1)*(n-1)+y;}
struct par{
    
    
	int x;ll dis;par(int xx=0,ll dd=0):x(xx),dis(dd){
    
    }
	bool operator <(const par &B)const{
    
    return dis>B.dis;}
};
priority_queue<par>q;
ll dis[maxn];
void dij()
{
    
    
	for(int i=1;i<=T;i++)dis[i]=inf;
	q.push(par(S,0));dis[S]=0;
	while(!q.empty()){
    
    
		par X=q.top();q.pop();int x=X.x;if(X.dis>dis[x])continue;
		for(int i=first[x];i;i=e[i].next){
    
    
			int y=e[i].y;
			if(dis[y]>dis[x]+e[i].z){
    
    
				dis[y]=dis[x]+e[i].z;
				q.push(par(y,dis[y]));
			}
		}
	}
}

int main()
{
    
    
	scanf("%d %d",&n,&m);S=(n-1)*(n-1)+1;T=S+1;
	for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)down[i][j]=left[i][j]=inf;
	for(int i=1;i<=m;i++){
    
    
		int x,y,c;char s[2];
		scanf("%d %d %s %d",&x,&y,s,&c);
		if(s[0]=='L')down[x][y]=c;
		else left[x][y]=c;
	}
	for(int i=1;i<=n-1;i++)
	for(int j=i;j<=n-1;j++)
	{
    
    
		if(j>i)ins(id(i,j),id(i+1,j),left[i+1][j+1]);
		if(j<n-1)ins(id(i,j),id(i,j+1),down[i][j+1]);
		if(j==n-1)buildroad(id(i,j),T,down[i][j+1]);
		if(i==1)buildroad(S,id(i,j),left[i][j+1]);
	}
	buildroad(S,T,inf);
	dij();if(dis[T]<inf)printf("%lld",dis[T]);else printf("-1");
}

J. Just Shuffle

找出一个置换,使得排列 1 , 2 , . . . , n 1,2,...,n 1,2,...,n 在进行 k k k 次置换后变成排列 A A A,保证 k k k 是大质数。

设这个置换为 B B B,那么其实,进行 k k k 次置换 B B B 等价于进行 1 1 1 次置换 A A A

考虑找出 A A A 中的环,对于一个大小为 S S S 的环,令 P P P 满足 P k ≡ 1 ( m o d S ) Pk\equiv 1\pmod S Pk1(modS),先将这个环旋转 P P P 次然后放到 B B B 中,这样得到的 B B B 在旋转 k k k 次之后就是 A A A 了。

由于 k k k 是大质数,所以逆元 P P P 总是存在的。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,k,a[maxn];
bool vis[maxn];
void exgcd(int a,int b,int &x,int &y){
    
    
	if(!b){
    
    x=1,y=0;return;}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
void go(int S){
    
    
	static vector<int>s;s.clear();
	for(int i=a[S];i!=S;i=a[i])s.push_back(i),vis[i]=true;
	s.push_back(S);vis[S]=true;
	int x,y,Size=s.size();exgcd(k,Size,x,y);
	x=((x-1)%Size+Size)%Size;//注意,这里不能写成%s.size(),因为x可能是负数,而s.size()是unsigned int类型的,直接模会出问题
	for(int i=S,j=0,next=a[i];j<s.size();j++,i=next,next=a[i])a[i]=s[(j+x)%s.size()];
}

int main()
{
    
    
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)if(!vis[i])go(i);
	for(int i=1;i<=n;i++)printf("%d ",a[i]);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/107534086