Atcoder 选做

发布时间 2023-05-23 10:51:13作者: 暗蓝色的星空

[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\) 从大到小排序,然后依次确定父亲。

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

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)\)

#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)\)

#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)\)

#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\) 即可。

// 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\) 就好了。

#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)\)

#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;
}