Loading

Atcoder 选做

[ARC103F] Distance Sums

首先显然 \(D_i\) 的最小值被重心取到,不妨以重心为根。

对于一条边连接的两个点 \(x,y\) ,不妨设这条边 \(x\) 侧的点数为 \(siz_x\)\(y\) 侧为 \(siz_y\)
那么 \(D_y=D_x+siz_x-siz_y=D_x+siz_x-(n-siz_x)=D_x+2\times siz_x -n\)

那么我们就可以知道以重心为根的时候,从 \(fa_x\) 转移到 \(x\) 的时候,\(2\times siz_x \leq n\),因此沿着叶向,\(D_i\) 递减。

那我们可以直接剥叶子,把 \(D_i\) 从大到小排序,然后依次确定父亲。

最后检查跟合不合法就好了。

code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
map<LL,int> node;
int n;
const int N = 1e5+7;
LL D[N];
int siz[N],fa[N],dep[N];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&D[i]);
		node[D[i]]=i;
	}
	sort(D+1,D+n+1);
	for(int i=n;i>=2;i--)
	{
		int x=node[D[i]];
		siz[x]++;
		fa[x]=node[D[i]+2ll*siz[x]-n];
		if(!fa[x]||fa[fa[x]])
		{
			cout<<-1;
			return 0;
		}
		siz[fa[x]]+=siz[x];
	}
	LL S=0;
	for(int i=2;i<=n;i++)
	{
		int x=node[D[i]];
		dep[x]=dep[fa[x]]+1;
		S+=dep[x];
	}
	if(S!=D[1])
	{
		cout<<-1;
		return 0;
	}
	for(int i=2;i<=n;i++)
	{
		int x=node[D[i]];
		printf("%d %d\n",x,fa[x]);
	}
	return 0;
}

[ARC102F] Revenge of BBuBBBlesort!

如果在 \(i\) 位置做过交换,则称 \(i\) 为一个中心点。

性质1:若 \(i\) 为中心点,则在原始排列 \(p\)\(p_i = i\)

\(\texttt{proof:}\)

\(i\) 进行过操作后 , 有 \(p_{i-1}<p_i<p_{i+1}\),不妨设现在\(p_{i}<i\),那么一定会在 \(p_{i-1}\) 进行一次交换。

也就是说,我们需要使得 \(p_{i-2}>p_{i-1}>p_i\),这需要在 \(p_{i-2}\) 进行一次交换,也就是说交换完变成 \(p_{i-3}<p_i<p_{i-2}<p_{i-1}\),那就无法操作了。

另一侧同理。

\(a_i=[p_i=i]\),那么我们只会对 \(a_i=1\) 的位置进行操作。

我们可以发现,如果 \(a_i=1,a_{i+1}=1\),那么在 \(i,i+1\) 都不会进行操作。

那么 我们可以把 \(a\) 换分成若干段,使得每个段内没有连续相同的\(a\)

那么各个段是独立的。

合法的条件:

  • [l,r] 内的数恰好覆盖 [l,r]

  • \(a_i=0\)的单独拿出来,如果这个序列中有长度大于2的下降子序列则不合法。

\(\texttt{proof:}\)

不妨设 \(p_i>p_j>p_k,i<j<k\)

如果我们现在先交换 \(p_i,p_j\),那么一定存在 \(a_x=1,i<x<j,p_j<p_x<p_i\)

然后再交换 \(p_i,p_k\),那么一定存在 \(a_y=1,j<y<k,p_k<p_y<p_i\)

现在我们要交换\(p_j,p_k\),但是因为 现在 \(p_j<p_x\),因此比不可能交换。

其他方案同理。

复杂度 \(O(n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
int n,p[N],a[N];
void badending()
{
	printf("No\n");
	exit(0);
}
int mx[N];
void check(int l,int r)
{

	int Max=0,SMax=0;
	for(int i=l;i<=r;i++)
	{
		if(p[i]<l||p[i]>r)badending();	
		if(a[i]==0)
		{
			mx[i]=Max;
			Max=max(Max,p[i]);
		}
	}
	int Min=1e9;
	for(int i=r;i>=l;i--)
	{
		if(a[i]==0)
		{
			if(mx[i]>p[i]&&p[i]>Min)badending();
			Min=min(Min,p[i]);
		}
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&p[i]);
		a[i]=(p[i]==i);
	}
	for(int i=1;i+2<=n;i++)
	if(a[i]==0&&a[i+1]==0&&a[i+2]==0)badending();
	for(int i=1;i<=n;i++)
	{
		int j=i;
		while(j+1<=n&&a[j]!=a[j+1])j++;
		check(i,j);
		i=j;
	}
	printf("Yes\n");
	return 0;
}

[ARC111F] Do you like query problems?

首先为了方便,转成期望。
根据期望的线性性,答案等于每个位置的贡献之和。
\(a_{t,i}\)\(t\) 次操作后 \(i\) 位置的数,总的操作种类数 \(S=\frac{n(n+1)}{2}\times (m+m+1)\)\(Q(i)=\frac{i(n-i+1)}{S}\)为一个当前操作是一个询问操作且包含了位置 \(i\)的概率,那么答案为:

\[\sum_{i=1}^nQ(i)\sum_{t=0}^{q-1}E(a_{t,i}) \]

根据整数概率公式,\(E(a_{t,i})=\sum_{w=1}P(a_{t,i}\ge w)\)
对于每个 \(w\) 独立计算,把大于等于 \(v\) 的数看成1,小于的看成0,那么最终就是 \(a_{t,i}=1\) 的概率。
设可能会导致 \(a_i\) 改变的操作为关键操作,那么就有两种,一种是 \(v\geq w\) 的取 \(\max\) 操作,一种是\(v<w\) 的取 $\min $ 操作,概率记为\(p1(i)=\frac{i(n-i+1)(m-w)}{S},p2(i)=\frac{i(n-i+1)w}{S}\)
那么 \(t\) 次操作后 \(a_i=1\) 就是要求至少有一个关键操作且最后一次操作是 \(p1(i)\),这个概率 \(p_{t,i,w}=\frac{p1(i)}{p1(i)+p2(i)}(1-(1-p1(i)-p2(i))^t)\)
\(s_{t,i}=\sum\limits_{w=1}^{m-1}p_{t,i,w}=\sum\limits_{w=1}^{m-1}\frac{m-w}{m}(1-(1-\frac{i(n-i+1)m}{S})^t)=\frac{m-1}{2}(1-(1-\frac{i(n-i+1)m}{S})^t)\)
枚举 \(i\),后面就是一个等比数列求和,复杂度\(O(n\log n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1000;
const int mod = 998244353;
const int inv2=(mod+1)/2;
int Pow(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1)res=1ll*res*a%mod;
		a=1ll*a*a%mod;
		b>>=1;
	}
	return res;
}
int n,m,q;
inline int mul(int a,int b){return 1ll*a*b%mod;}
inline int inv(int a){return Pow(a,mod-2);}
int Sum(int a,int b)
{
	if(a==1) return b;
	return mul((Pow(a,b)-1+mod)%mod,inv(a-1));
}
int main()
{
	cin>>n>>m>>q;
	int S=mul(n,mul(n+1,mul(inv2,m+m+1)));
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		int Q=mul(mul(i,n-i+1),inv(S));
		int P=mul(m-1,inv2);
		int A=mul(mul(i,n-i+1),mul(m,inv(S)));
		int W=Sum((1-A+mod)%mod,q);
		ans=(ans+mul(P,mul(Q,(q-W+mod)%mod)))%mod;
	}
	cout<<mul(ans,Pow(S,q));
	return 0;
}

[ARC131F] ARC Stamp

考虑倒序操作,将被操作过的位置设为 \(?\) ,那么可以被操作的有如下几种:\(\texttt{ARC,AR?,?RC,A?C,A??,?R?,??C}\)

考虑将序列划分成若干段,每段是上述的一种,那么每段是独立的,把所有被操作过的段的方案数。

因为每一段是否被选择和他前后两端都有关系,因此不妨每三个计算一次贡献。

考虑 \(dp\)\(f_{i,x,y}\) 表示前 \(i\) 段,第 \(i-1\) 段的状态是 \(x\),第 \(i\) 段的状态是 \(y\) 的方案数,转移时枚举下一段的状态。

复杂度 \(O(n^2)\)

code
#include<bits/stdc++.h>
using namespace std;
int n,k;
const int N = 5040;
char S[N];
int a[N],b[N];
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
PII range[N];
int m=0;
int dp[N][N][2][2];
int bef[N][N];
const int mod = 998244353;
int w[N];
int calc(int i,int x,int y,int z)
{
    if(!i)return 1;
    int A=range[i-1].second,B=range[i].first;
    int C=range[i].second,D=range[i+1].first;
    bool must1=0;
    bool must0=0;
    if(x==1&&bef[A][B]==2)must1=1;
    if(z==1&&bef[C][D]==1)must1=1;
    if(x==0&&bef[A][B]==1)must0=1;
    if(z==0&&bef[C][D]==2)must0=1;
    if(must0&&must1)return 0;
    if(must0) return y==0;
    if(must1) return (y==1)*w[i];
    if(y==0) return 1;
    return w[i]-1;
}
int main()
{
    scanf("%s",S+1);
    n=strlen(S+1);
    cin>>k;k=min(k,n);
    for(int i=1;i<=n;i++)
    if(S[i]=='A')a[i]=1;
    else if(S[i]=='R')a[i]=2;
    else a[i]=3;
    for(int i=1;i<=n;i++)b[i]=a[i];
    while(1)
    {
        bool flag=0;
        for(int i=1;i+2<=n;i++)
        {
            if(b[i]==1&&b[i+1]==2&&b[i+2]==3)
            {
                range[++m]=mk(i,i+2);
                b[i]=b[i+1]=b[i+2]=0;
                flag=1;
                continue;
            }
            if(b[i]==0&&b[i+1]==2&&b[i+2]==3)
            {
                range[++m]=mk(i+1,i+2);
                bef[i][i+1]=1;
                b[i]=b[i+1]=b[i+2]=0;
                flag=1;
                continue;
            }
            if(b[i]==1&&b[i+1]==2&&b[i+2]==0)
            {
                range[++m]=mk(i,i+1);
                bef[i+1][i+2]=2;
                b[i]=b[i+1]=b[i+2]=0;
                flag=1;
                continue;
            }
            if(b[i]==1&&b[i+1]==0&&b[i+2]==0)
            {
                range[++m]=mk(i,i);
                bef[i][i+1]=2;
                b[i]=b[i+1]=b[i+2]=0;
                flag=1;
                continue;
            }
            if(b[i]==0&&b[i+1]==2&&b[i+2]==0)
            {
                bef[i][i+1]=1;
                bef[i+1][i+2]=2;
                range[++m]=mk(i+1,i+1);
                b[i]=b[i+1]=b[i+2]=0;
                flag=1;
                continue;
            }
            if(b[i]==0&&b[i+1]==0&&b[i+2]==3)
            {
                bef[i+1][i+2]=1;
                range[++m]=mk(i+2,i+2);
                b[i]=b[i+1]=b[i+2]=0;
                flag=1;
                continue;
            }
        }
        if(!flag)break;
    }
    sort(range+1,range+m+1);
    for(int i=1;i<=m;i++)
    {
        w[i]=1;
        for(int u=range[i].first;u<=range[i].second;u++)w[i]=w[i]*3;
    }
    dp[0][0][0][0]=1;
    for(int i=1;i<=m;i++)
    for(int j=0;j<=min(i-1,k);j++)
    for(int x=0;x<=1;x++)
    for(int y=0;y<=1;y++)
    if(dp[i-1][j][x][y])
    {
        for(int z=0;z<=1;z++)
        {
            dp[i][j+z][y][z]=(dp[i][j+z][y][z]+1ll*dp[i-1][j][x][y]*calc(i-1,x,y,z)%mod)%mod;
        }
    }
    int res=0;
    for(int i=0;i<=k;i++)
    for(int c=0;c<=1;c++)
    for(int d=0;d<=1;d++)
    res=(res+1ll*dp[m][i][c][d]*calc(m,c,d,0)%mod)%mod;
    cout<<res;
    return 0;
}

[ARC085F] NRE

简单题,设 \(dp_{i,j}\) 表示前 \(i\) 个位置,覆盖最远的区间覆盖到 \(j\) 的最小代价,转移是简单的。
那么只需要线段树优化 \(dp\) 即可。

code
// LUOGU_RID: 111038731
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
typedef long long LL;
void ckmax(int &x,int v){x=max(x,v);}
int tag[N*4],mn[N*4],upd[N*4];
const int INF = 1e8;
void pushtag(int k,int v)
{
	mn[k]+=v;
	tag[k]+=v;
	upd[k]+=v;
}
void pushmin(int k,int v)
{
	mn[k]=min(mn[k],v);
	upd[k]=min(upd[k],v);
}
void pushdown(int k)
{
	if(tag[k])
	{
		pushtag(k<<1,tag[k]);
		pushtag(k<<1|1,tag[k]);
		tag[k]=0;
	}
	if(upd[k]<=INF)
	{
		pushmin(k<<1,upd[k]);
		pushmin(k<<1|1,upd[k]);
		upd[k]=INF;
	}
}
void pushup(int k)
{
	mn[k]=min(mn[k<<1],mn[k<<1|1]);
}
void Add(int k,int l,int r,int L,int R,int v)
{
	if(L<=l&&r<=R)
	{
		pushtag(k,v);
		return;
	}	
	pushdown(k);
	int mid=(l+r)>>1;
	if(L<=mid)Add(k<<1,l,mid,L,R,v);
	if(R>mid)Add(k<<1|1,mid+1,r,L,R,v);
	pushup(k);
}
void Min(int k,int l,int r,int L,int R,int v)
{
	if(L<=l&&r<=R)
	{
		pushmin(k,v);
		return;
	}	
	pushdown(k);
	int mid=(l+r)>>1;
	if(L<=mid)Min(k<<1,l,mid,L,R,v);
	if(R>mid)Min(k<<1|1,mid+1,r,L,R,v);
	pushup(k);
}
int Qry(int k,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)return mn[k];
	pushdown(k);
	int res=INF;
	int mid=(l+r)>>1;
	if(L<=mid)res=min(res,Qry(k<<1,l,mid,L,R));
	if(R>mid)res=min(res,Qry(k<<1|1,mid+1,r,L,R));
	return res;
}
void Build(int k,int l,int r)
{
	mn[k]=INF;
	upd[k]=INF;
	tag[k]=0;
	if(l==r)return;
	int mid=(l+r)>>1;
	Build(k<<1,l,mid);
	Build(k<<1|1,mid+1,r);
}
int n,w[N],m,dp[N];
void ADD(int l,int r,int v)
{
	Add(1,0,n,l,r,v);
}
void MIN(int l,int r,int v)
{
	Min(1,0,n,l,r,v);
}
int QRY(int l,int r)
{
	return Qry(1,0,n,l,r);
}
vector<int> G[N];
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.ans","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%d",&w[i]);
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		int l,r;
		scanf("%d %d",&l,&r);
		G[l].push_back(r);
	}
	Build(1,0,n);
	MIN(0,0,0);
	for(int i=1;i<=n;i++)
	{
		for(auto x:G[i])MIN(x,x,QRY(0,x-1));
		//a[i]=0
		if(w[i]==1)ADD(0,i-1,1);
		//a[i]=1
		if(w[i]==0)ADD(i,n,1);
	}
	cout<<QRY(0,n);
	return 0;
}

[ARC094F] Normalization

可以发现如果把 \(a,b,c\) 设为 \(0,1,2\),那么操作不会改变字符串中数字总和 \(\%3\) 意义下的数。
首先特判掉 \(S\) 都是一个字符以及 \(|S|<=3\) 的情况。
那么一个串 \(T\) 可以被得到的必要条件是: \(T\) 中存在一对相邻且相等的位置,且两个序列 \(\%3\) 相等。
猜一下这个也是充分的,事实也确实如此。
因此直接 \(dp\) 就好了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
const int mod = 998244353;
char s[N];
int n;
int dp[N][3][2][3];
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	bool flag=1,flag2=0;
	for(int i=1;i<n;i++)
	{
		flag&=(s[i]==s[i+1]);
		flag2|=(s[i]==s[i+1]);
	}
	if(flag)
	{
		printf("1\n");
		return 0;
	}
	if(n==2)
	{
		cout<<2;
		return 0;
	}
	if(n==3)
	{
		if(s[1]!=s[2]&&s[2]!=s[3]&&s[1]!=s[3])cout<<3;
		else if(s[1]==s[2])cout<<6;
		else if(s[2]==s[3])cout<<6;
		else cout<<7;
		return 0;
	}
	for(int c=0;c<=2;c++)dp[1][c][0][c]=1;
	int w=0;
	for(int i=1;i<=n;i++)w=(w+s[i]-'a')%3; 
	for(int i=2;i<=n;i++)
	{
		for(int v=0;v<=2;v++)
		for(int c=0;c<=1;c++)
		for(int a=0;a<=2;a++)
		if(dp[i-1][v][c][a])
		{
			for(int b=0;b<=2;b++)
			dp[i][(v+b)%3][c|(a==b)][b]=(dp[i][(v+b)%3][c|(a==b)][b]+dp[i-1][v][c][a])%mod;
		}
	}
	int ans=0;
	for(int c=0;c<=2;c++)ans=(ans+dp[n][w][1][c])%mod;
	cout<<(ans+1-flag2)%mod;
	return 0;
}

[ARC082F] Sandglass

\(f_{a}(t)\) 为初始值为 \(a\) 的情况下,时间 \(t\) 时的值。
那么整个图像就是一条折线,碰到 \(y=0\)\(y=X\) 就会顶着,而遇到 \(x=r_i\) 就会改变斜率。
容易发现对于 \(0\leq a\leq b\leq X,t\in Z,f_a(t)\leq f_b(t)\)
同时,如果在某一时刻 \(t\)\(f_a(t)=f_0(t)\) 或者 \(f_a(t)=f_X(t)\),那么之后,\(f_a(t)\) 就会一直和 \(f_0(t)\)\(f_X(t)\) 相等,而如果从来没有,那么说明\(f_a(t)\)一直没有触碰上下界,因此当前位置可以直接求出,设为 \(x\)
那么如果 \(x<f_0(t)\),最终就是 \(f_0(t)\),如果 \(>f_X(t)\),最终就是 \(f_X(t)\),否则就是 \(x\)
复杂度 \(O(n+m)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
LL X,O,n,q,fo,fx,f,p[N];
int main()
{
	scanf("%lld",&X);
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld",&p[i]);
	fo=O;fx=X;f=0;
	int now=0;
	cin>>q;
	while(q--)
	{
		LL t,a;
		scanf("%lld %lld",&t,&a);
		while(now+1<=n&&p[now+1]<=t)
		{
			if(now&1)
			{
				fx=min(X,fx+p[now+1]-p[now]);
				fo=min(X,fo+p[now+1]-p[now]);
				f+=p[now+1]-p[now];
			}
			else 
			{
				fx=max(O,fx-p[now+1]+p[now]);
				fo=max(O,fo-p[now+1]+p[now]);
				f-=p[now+1]-p[now];
			}
			now++;
		}
		LL Fx=0,Fo=0,Fa=0;
		if(now&1)
		{
			Fx=min(X,fx+t-p[now]);
			Fo=min(X,fo+t-p[now]);
			Fa=f+t-p[now];
		}
		else 
		{
			Fx=max(O,fx-t+p[now]);
			Fo=max(O,fo-t+p[now]);
			Fa=f-t+p[now];
		}
		Fa+=a;
		Fa=min(Fa,Fx);
		Fa=max(Fa,Fo);
		printf("%lld\n",Fa); 
	}
	return 0;
}

[ARC115F] Migration

考虑二分答案,对于两个可以互相到达的状态,在这两个状态之间连一条边,那么就是询问 \(S,T\) 两个状态是否在同一个联通快内。
这不好直接判断,考虑求出 \(S,T\) 可以到达的,\(\sum a_i\) 最小的状态(相等比较字典序),如果相等说明,\(S,T\)在同一个联通块里。

对于每个点 \(x\),求出 \(y=f_x\) ,满足 \(h_y<h_x\)\(x\to y\) 上最大的点权最小 。

这样,我们每次如果满足限制就\(s_i\to f_{s_i},t_i\to f_{t_i}\),因为点权和减少所以一定是不劣的。

用堆维护,每次取出最小值即可。

考虑去掉二分,实际上每次取出 \(S,T\) 里较小的一个更新即可。

复杂度 \(O(n^2+nk\log k)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 2020;
typedef long long LL;
struct edge
{
	int y,next;
}e[2*N];
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
int n,k;
int h[N];
bool cmp(int x,int y)
{
	if(h[x]==h[y])return x<y;
	return h[x]<h[y];
}
int Min(int x,int y)
{
	if(h[x]==h[y]) return min(x,y);
	if(h[x]<h[y]) return x;
	return y;
}
int w[N],f[N],val[N];
void dfs(int x,int pre)
{
	val[x]=h[x];
	if(pre)val[x]=max(val[x],val[pre]);
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==pre)continue;
		dfs(y,x);
	}
}
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
priority_queue<PII,vector<PII>,greater<PII> >A,B;
int a[N],b[N]; 
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%d",&h[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		add(x,y);
		add(y,x);
	}
	for(int x=1;x<=n;x++)
	{
		dfs(x,0);
		for(int y=1;y<=n;y++)
		if(cmp(y,x))
		{
			if(!f[x]||val[y]-h[x]<w[x])
			{
				f[x]=y;
				w[x]=val[y]-h[x];
			}
		}
	}
	int K;
	scanf("%d",&K);
	LL S=0,T=0,C=0;
	for(int i=1;i<=K;i++)
	{
		scanf("%d %d",&a[i],&b[i]);
		S+=h[a[i]];T+=h[b[i]];
		if(f[a[i]])A.push(mk(w[a[i]],i));
		if(f[b[i]])B.push(mk(w[b[i]],i));
		C+=(a[i]!=b[i]);
	}
	LL ans=max(S,T);
	while(C)
	{
		if(B.empty()||(!A.empty()&&X(A.top())<X(B.top())))
		{
			int i=A.top().second;A.pop();
			ans=max(ans,S+w[a[i]]);
			C-=(a[i]!=b[i]);S-=h[a[i]];
			a[i]=f[a[i]];
			C+=(a[i]!=b[i]);S+=h[a[i]];
			if(f[a[i]])A.push(mk(w[a[i]],i));
		}
		else 
		{
			int i=B.top().second;B.pop();
			ans=max(ans,T+w[b[i]]);
			C-=(a[i]!=b[i]);T-=h[b[i]];
			b[i]=f[b[i]];
			C+=(a[i]!=b[i]);T+=h[b[i]];
			if(f[b[i]])B.push(mk(w[b[i]],i));	
		}
	} 
	cout<<ans;
	return 0;
}

[ARC106F] Figures

和度数有关,考虑 \(prufer\) 序列。
如果 \(i\) 里面选了 \(a_i\) 个洞,那么最终 \(i\) 的度数就是 \(a_i\),最终在 \(prufer\) 序列里就会出现 \(a_i-1\) 次,同时这 \(a_i\) 个洞的匹配是有序匹配,所以方案数也要乘上 \(a_i!\)

显然所有点的度数的和就是 \(2(n-1)\)
\(F_i(x)\) 为第 \(i\) 个点的生成函数, 那么答案就是\(ans=(n-2)![x^{2(n-1)}](n-2)!\prod\limits_{i=1}^nF_i(x)\)

\[F_i(x)=\sum\limits_{j=1}\frac{\binom{d_i}{j}j!}{(j-1)!}x^j=\sum\limits_{j=1}j\binom{d_i}{j}x_j \]

考虑吸收恒等式,

\[F_i(x)=d_i\sum\limits_{j=1}\binom{d_i-1}{j-1}x^j=x\times d_i\sum\limits_{j=0}\binom{d_i-1}{j}x^j=xd_i(1+x)^{d_i-1} \]

\[ans=(n-2)!\prod\limits_{i=1}^nd_i[x^{n-2}]\prod\limits_{i=1}^n(1+x)^{d_i-1} \]

\(S=\sum d_i\)

\[ans=(n-2)!\prod\limits_{i=1}^nd_i[x^{n-2}](1+x)^{S-n}=(n-2)!\prod\limits_{i=1}^nd_i\binom{S-n}{n-2} \]

可以 \(O(n)\) 计算了。

code
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int n;
int main()
{
	cin>>n;
	int mul=1,sum=0;
	for(int i=1;i<=n;i++)
	{
		int d;
		scanf("%d",&d);
		mul=1ll*mul*d%mod;
		sum=(sum+d)%mod;
	}
	for(int i=0;i<=n-3;i++)mul=1ll*mul*(sum-n-i+mod)%mod;
	cout<<mul;
	return 0;
}

[ARC127F] ±AB

  • \(A+B-1\leq M\)

引理一:若 \(x=V+pA+qB\in [0,M]\) ,则 \(V\) 可以变成 \(x\)

\(p,q\) 同号,显然成立。
若不同号,不妨设 \(p>0,q<0\),那么我们可以一直减 \(B\) 直到达到上限或者不能减了,若达到上限,此时一定可以加 \(p\)\(A\),若不能减了,当前的数字 \(v<B\),那么一定可以执行一次加 \(A\),因为 \(v+A\leq A+B-1\leq M\),因此我们就不停地 \(-B,+A\) 直到合法即可。

因为 \(\gcd(A,B)=1\),所以根据裴蜀定理,\(x\) 可以取遍 \([0,M]\)

  • \(A+B-1>M\)

不妨令 \(V=V\bmod A\),显然这和原问题等价。
显然第一步不能选择 \(-A\),且因为 \(A<B\),第一步也不能选择 \(-B\)
不妨设第一次选择了 \(+A\),那么接下来不能选择 \(-A\),因为重复了,也不能 \(+B\),因为 \(A+B-1>M\)。那么只能在 \(+A,-B\) 中选一个,并且显然两个不能同时选,因此对于每个数,都只有唯一的出边,另一种走法 \(+B,-A\) 同理。

引理二:转移图无环

不妨设最小环走了 \(x\)\(+A\)\(y\)\(-B\),则 \(Ax=By\),因为 \(\gcd(A,B)=1\),该方程最小正整数解为 \(B,A\)
但是因为 \(A+B>M+1\),所以环一定重复经过了某些点,与环是最小的矛盾。

引理三:两种走法无交

因为两种走法起点相同,所以若相交,那么一定有环,与上矛盾。

因此可以对两种走法分别计算最远走多少步再加起来就是答案,以 \(+A,-B\) 为例。

不妨设进行了 \(k\)\(+A\),因为走不动了,所以最终的数 \(x\) 一定满足 \(x\in [m-A+1,B-1]\),那么一定进行了 $\lfloor \frac{V+kA}{B} \rfloor $ 次 \(-B\)

也就是说,我们要找最小的 \(k\),满足 $(V+kA)\bmod B+A\geq M+1 $ 。

\(V0=V\bmod B\),若 \(V0+A>M\),显然此时答案为 $\lfloor \frac{V}{B} \rfloor $ 。
否则:

引理四:\(V0+(kA\bmod B)<B\)

若不然,我们先减去 \(B\),然后 \(+A\),一定合法,矛盾。

因此 \((V+kA)\bmod B=V0+(kA\bmod B)\),转为计算 \(L\leq (kA)\bmod B\leq R\) 的最小的 \(k\)

考虑使用类欧求解。

\(B=pA+q,t=\lfloor \frac{kA}{B} \rfloor\),那么 \((kA)\bmod B=kA-Bt=kA-(pA+q)t=A(k-tp)-tq\),可以变成 \(tq\bmod A\in [L',R']\),递归计算。

code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int Euclid(int A,int B,int L,int R)
{
	if(A==0) return 0;
	A%=B;
	if((L-1)/A!=R/A) return (L+A-1)/A;
	int t=Euclid(B%A,A,(A-R%A)%A,(A-L%A)%A);
	return (1ll*t*B+L+A-1)/A;
}
int walk(int A,int B,int V,int M)
{
	int V0=V%M;
	if(V0+A>M) return V/B;
	int k=Euclid(A,B,M-A-V0+1,B-V0-1);
	return k+(V+1ll*A*k)/B;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int A,B,V,M;
		scanf("%d %d %d %d",&A,&B,&V,&M);
		if(A+B-1<=M)printf("%d\n",M+1);
		else printf("%d\n",walk(A,B,V%A,M)+walk(B,A,V%A,M)+1); 
	}
	return 0;
} 

[ARC132E] Paw

考虑最终形态,一定是一段前缀是 \(<\),一段后缀是 \(>\),中间保持不变且没有洞。
如果有 \(k\) 个洞,那么一共有 \(k+1\) 种合法的形态,贡献是好算的,重点是算这种情况发生的概率。
容易发现前缀和后缀是相同的,设 \(f_i\) 表示把 \(i\) 个洞填满,不覆盖到 \(i\) 之后的概率。
如果把覆盖洞的顺序携程一个序列,那么要求就是,所有的后缀最大值都必须朝左。
考虑 \(1\) 号洞,除非他在最后,否则不可能是后缀最大值,因此 \(f_i=f_{i-1}\frac{2i-1}{2i}\)
复杂度 \(O(n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
int n,m;
char s[N];
int f[N],a[N],p[N];
const int mod = 998244353;
int inv[N];
const int inv2=(mod+1)/2;
int main()
{
    cin>>n;
    scanf("%s",s+1);
    p[++m]=0;
    for(int i=1;i<=n;i++)
    {
        a[i]=a[i-1];
        if(s[i]=='<')a[i]++;
        if(s[i]=='.')p[++m]=i;
    }
    p[++m]=n+1;
    inv[1]=1;
    for(int i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    f[0]=1;
    for(int i=1;i<=n;i++)
    f[i]=1ll*f[i-1]*(2*i-1)%mod*inv2%mod*inv[i]%mod;
    int ans=0;
    for(int i=1;i<m;i++)
    {
        int coef=1ll*f[i-1]*f[m-i-1]%mod;
        ans=(ans+1ll*(p[i]+a[p[i+1]-1]-a[p[i]])*coef%mod)%mod;
    }
    cout<<ans;
    return 0;
}

[ARC080F] Prime Flip

首先做异或差分,那么每次可以选择 \(i,j\),满足 \(j-i\in prime\),反转 \(b_i,b_j\),最终要把所有数字都变成 0。
考虑对于 \(j-i\) 分类讨论:

  • \(j-i\) 是奇质数
    最优方案就是 \(1\) .
  • \(j-i\) 是偶数
    \(j-i=2\),我们可以分别操作长度为 \(5,3\) 的区间。
    \(j-i=4\),我们可以分别操作长度为 \(7,3\) 的区间。
    否则根据哥德巴赫猜想一定可以把区间分成两个奇质数之和,因此操作次数总是为 \(2\)
  • \(j-i\) 是非质数的奇数
    同上可证明一定可以操作次数为 \(3\)
    有一个贪心的做法是:先操作第一类,再操作第二类,再操作第三类,可以证明这样总是优的。
    注意到满足第一类的两个数奇偶性一定不同,用二分图匹配求出最大匹配,并且第二类两个数是相等的,因此一定是在剩下的数里尽可能每一类两两匹配,最后要么什么也没有,要么剩下一对,用第三类就行了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 220;
struct edge
{
	int y,next;
}e[N*N];
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
int match[N];
bool vis[N];
int n;
bool isprime(int x)
{
	if(x<3)return 0;
	for(int i=2;i*i<=x;i++)
	if(x%i==0) return 0;
	return 1;
}
int dfs(int x)
{
	if(vis[x])return 0;
	vis[x]=1;
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(!match[y]||dfs(match[y]))
		{
			match[y]=x;
			return 1;
		}
	}
	return 0;
}
int p[N],a[N];
int main()
{
	int m=0;
	cin>>m;
	a[0]=-1e9;
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&a[i]);
		if(a[i]>a[i-1]+1)
		{
			if(i>1)p[++n]=a[i-1]+1;
			p[++n]=a[i];
		}
	}
	p[++n]=a[m]+1;
	for(int i=1;i<=n;i++)if(p[i]%2==0)
	for(int j=1;j<=n;j++)if(p[j]%2==1)
	if(isprime(abs(p[i]-p[j])))add(i,j);
	int ans=0;
	int A=0,B=0;
	for(int i=1;i<=n;i++)
	if(p[i]%2==0)
	{
		memset(vis,0,sizeof(vis));
		ans+=dfs(i);
		A++;
	}
	else B++;
	A-=ans;B-=ans;
	ans+=(A/2)*2;A%=2;
	ans+=(B/2)*2;B%=2;
	if(A&&B)ans+=3;
	cout<<ans;
	return 0;
}

[ARC122F] Domination

巨大神仙题。
首先,如果一个红点右上方还有其他点,这个点可以被删掉,那么剩下的点满足横坐标递增,纵坐标递减。
也就是说,对于一个蓝点,它左下方的红点构成了一个区间。
首先考虑 \(k=1\) 的情况。
注意到一个点覆盖两个区间 \([l_1,r_1],[l_2,r_2]\) 的代价和不小于覆盖 \([l_1,r_2]\) 的,因此不妨认为一个点可以被多次使用,这不影响答案。
考虑一个 \(dp\),设 \(f_i\) 为覆盖前 \(i\) 个红色点且最后一个区间右端点为 \(i\) 的最小花费,转移时枚举左端点 \(j\),以及选取的石头 \(k\),转移为 \(f_i=f_{j-1}+ \max(x_i-x_k,0)+\max(y_j-y_k,0)\)
考虑把每个石头拆成两个点,分别代表其 \(x\) 坐标和 \(y\) 坐标,然后分别从小到大排序。
对于 \(x\) 坐标拆成的点,从排序后第 \(i\) 个连向 \(i+1\),代价为 \(x_{i+1}-x_i\),再连反向边,代价为 \(0\) .
对于 \(y\) 坐标拆成的点,从排序后第 \(i\) 个连向 \(i-1\),代价为 \(y_i-y_{i-1}\),再连反向边,代价为 \(0\) .
对于每个蓝点,从对应 \(y\) 点向 \(x\) 点连 0 边。
那么从 \(y_j\)\(x_i\) 的最短路就是,\(\max(x_i-x_k,0)+\max(y_j-y_k,0)\)
在此基础上连边 \(x_i,y_{i+1}\),那么从 \(y_1\)\(x_n\) 的最短路就是答案。
对于 \(k\) 任意的情况,等价于找出 \(k\) 条路径覆盖就行了,在上图基础上限制蓝点的边流量为 \(1\),其余边流量为 \(inf\),求流量为 \(k\) 的最小费用最大流就可以了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 4e5+7;
typedef long long LL;
const int INF = 1e9;
int tot=0,S,T;
namespace flow
{
    struct edge
    {
        int y,next,w;
    }e[6*N];
    int link[N],t=1;
    int f[6*N];
    void add(int x,int y,int v,int w)
    {
        e[++t].y=y;
        e[t].w=w;
        f[t]=v;
        e[t].next=link[x];
        link[x]=t;
    }
    void Link(int x,int y,int v,int w)
    {
        add(x,y,v,w);
        add(y,x,0,-w);
    }
    #define PII pair<LL,int>
    #define mk(x,y) make_pair(x,y)
    #define X(x) x.first
    #define Y(x) x.second
    priority_queue<PII> q;
    LL d[N],h[N];
    bool vis[N];
    int pre[N];
    void dijkstra()
    {
        for(int i=1;i<=tot;i++)
        {
            d[i]=1e18;
            vis[i]=0;
        }
        d[S]=0;
        q.push(mk(-d[S],S));
        while(!q.empty())
        {
            int x=q.top().second;
            q.pop();
            if(vis[x])continue;
            vis[x]=1;
            for(int i=link[x];i;i=e[i].next)
            {
                int y=e[i].y;
                LL v=e[i].w+h[x]-h[y];
                if(f[i]&&d[y]>d[x]+v)
                {
                    d[y]=d[x]+v;
                    pre[y]=i;
                    q.push(mk(-d[y],y));
                }
            }
        }
    }
    LL dinic(int K)
    {
        LL ans=0;
        while(K--)
        {
            dijkstra();
            ans+=d[T]-(h[S]-h[T]);
            for(int i=T;i!=S;i=e[pre[i]^1].y)
            {
                f[pre[i]]--;
                f[pre[i]^1]++;
            }
            for(int i=1;i<=tot;i++)
            h[i]+=d[i];
        }
        return ans;
    }
}
struct node
{
    int x,y;
}A[N],B[N],C[N];
int n,m,K;
bool cmp(node X,node Y)
{
    if(X.x==Y.x) return X.y>Y.y;
    return X.x>Y.x;
}
struct Pot
{
    int x,o;
}Px[N*2],Py[N*2];
int Cx=0,Cy=0;
int Aid[N][2],Bid[N][2];
bool cmp2(Pot X,Pot Y)
{
    return X.x<Y.x;
}
int main()
{
    cin>>n>>m>>K;
    for(int i=1;i<=n;i++)scanf("%d %d",&C[i].x,&C[i].y);
    for(int i=1;i<=m;i++)scanf("%d %d",&B[i].x,&B[i].y);
    sort(C+1,C+n+1,cmp);
    LL mx=-1;
    int nn=0;
    for(int i=1;i<=n;i++)
    {
        if(C[i].y>mx)
        {
            mx=C[i].y;
            A[++nn]=C[i];
        }
    }
    n=nn;
    reverse(A+1,A+n+1);
    for(int i=1;i<=n;i++)
    {
        Aid[i][0]=++tot;
        Aid[i][1]=++tot;
        Px[++Cx]=(Pot){A[i].x,Aid[i][0]};
        Py[++Cy]=(Pot){A[i].y,Aid[i][1]};
    }
    for(int i=1;i<=m;i++)
    {
        Bid[i][0]=++tot;
        Bid[i][1]=++tot;
        Px[++Cx]=(Pot){B[i].x,Bid[i][0]};
        Py[++Cy]=(Pot){B[i].y,Bid[i][1]};
    }
    sort(Px+1,Px+Cx+1,cmp2);
    sort(Py+1,Py+Cy+1,cmp2);
    S=++tot;T=++tot;
    for(int i=1;i<Cx;i++)
    {
        flow::Link(Px[i].o,Px[i+1].o,INF,Px[i+1].x-Px[i].x);
        flow::Link(Px[i+1].o,Px[i].o,INF,0);
    }
    for(int i=1;i<Cy;i++)
    {
        flow::Link(Py[i].o,Py[i+1].o,INF,0);
        flow::Link(Py[i+1].o,Py[i].o,INF,Py[i+1].x-Py[i].x);
    }
    for(int i=1;i<n;i++)
    flow::Link(Aid[i][0],Aid[i+1][1],INF,0);
    for(int i=1;i<=m;i++)
    flow::Link(Bid[i][1],Bid[i][0],1,0);
    flow::Link(S,Aid[1][1],K,0);
    flow::Link(Aid[n][0],T,K,0);
    cout<<flow::dinic(K);
    return 0;
}

</details>
posted @ 2023-05-23 10:42  Larunatrecy  阅读(12)  评论(0)    收藏  举报