AtCoder Grand Contest 028

Preface

感觉这场的题目都挺好的,都需要想上一会才能出解

9/10:ABC solved

9/13:DE solved,可惜都不会做

F(F2)看着就不可做,弃了弃了准备初赛去了


A - Two Abbreviations

简单画一画我们就会发现如果(l=operatorname{lcm}(n,m))时无解那么必然无解,显然很容易判断可行性

#include<cstdio>
#include<map>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,m,l; char s[N],t[N]; map <int,char> res;
inline int gcd(CI n,CI m)
{
	return m?gcd(m,n%m):n;
}
signed main()
{
	RI i; scanf("%lld%lld%s%s",&n,&m,s+1,t+1); l=n*m/gcd(n,m);
	for (i=1;i<=n;++i) res[(i-1)*(l/n)+1]=s[i];
	for (i=1;i<=m;++i) if (res.count((i-1)*(l/m)+1)&&res[(i-1)*(l/m)+1]!=t[i])
	return puts("-1"),0; return printf("%lld",l),0;
}


B - Removing Blocks

刚开始想蠢了出了各种奇怪做法,但都没有涉及到本质的优化

后来发现我们考虑两个位置(i,j),考虑设一个(f(i,j))表示删除(i)时对(j)造成影响的概率

显然此时的充要条件是(i)([i,j])中第一个被删除的数,那么(f_{i,j}=frac{1}{|i-j|+1})

而最后我们要求的每个位置(j)的贡献就是(n! imes sum_{i=1}^n f_{i,j}),只需要求出(frac{1}{i})的前缀和即可

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+7;
int n,a[N],fn,inv[N],ans;
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (fn=inv[0]=inv[1]=1,i=2;i<=n;++i)
	inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
	for (i=2;i<=n;++i) fn=1LL*fn*i%mod,(inv[i]+=inv[i-1])%=mod;
	for (i=1;i<=n;++i) (ans+=1LL*(inv[i]+inv[n-i+1]-1)*fn%mod*a[i]%mod)%=mod;
	return printf("%d",ans),0;
}


C - Min Cost Cycle

稍微找下性质就可以rush的题

考虑我们根据一个点的(A_i,B_i)被选中为最终边的的类别进行分类:

  1. (A_i,B_i)均被选
  2. (A_i)被选,(B_i)未被选
  3. (B_i)被选,(A_i)未被选
  4. (A_i,B_i)均未被选

显然我们发现最后的环要么全是(2)类点或(3)类点,这种情况随便计算

要么有(xge 1)(1)类点和(4)类点,剩下的就随便选

考虑后面这种情况显然可以把所有数排序一下在枚举至少选的一对(A_i,B_i)即可

#include<cstdio>
#include<algorithm>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct element
{
	int v,p;
	friend inline bool operator < (const element& A,const element& B)
	{
		return A.v<B.v;
	}
}d[N<<1]; int n,a[N],b[N],p[N],q[N]; long long ans,sa,sb,ret;
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i)
	scanf("%d%d",&a[i],&b[i]),d[i].v=a[i],d[i].p=i,
	d[n+i].v=b[i],d[n+i].p=n+i,sa+=a[i],sb+=b[i];
	for (sort(d+1,d+(n<<1)+1),i=1;i<=(n<<1);++i)
	if (d[i].p<=n) p[d[i].p]=i; else q[d[i].p-n]=i;
	for (i=1;i<=n;++i) ret+=d[i].v;
	for (ans=min(sa,sb),i=1;i<=n;++i)
	if (p[i]<=n&&q[i]<=n) ans=min(ans,ret); else
	if (p[i]<=n&&q[i]>n) ans=min(ans,ret+d[q[i]].v-(p[i]!=n?d[n].v:d[n-1].v)); else
	if (p[i]>n&&q[i]<=n) ans=min(ans,ret+d[p[i]].v-(q[i]!=n?d[n].v:d[n-1].v)); else
	ans=min(ans,ret+d[p[i]].v+d[q[i]].v-d[n].v-d[n-1].v);
	return printf("%lld",ans),0;
}


D - Chords

怎么这么蠢这种类型的题目都做了多少遍了还是没想到

显然我们可以单独考虑一个区间([l,r])为联通块的方案数,考虑当我们要减去的就是这个区间内有和外界连边的方案数以及这个区间内形成了多个小联通块的方案数

前者显然很好计算,后者我们调整下枚举顺序,从区间长度小的开始枚举即可,然后容斥减一减即可

复杂度(O(n^3))

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=605,mod=1e9+7;
int n,k,x,y,f[N][N],p[N],g[N],sum[N],ans;
inline void DP(CI l,CI r)
{
	if ((sum[r]-sum[l-1])&1) return; RI i;
	for (i=l;i<=r;++i) if (p[i]&&(p[i]<l||p[i]>r)) return;
	int ret=g[sum[r]-sum[l-1]]; for (i=l+1;i<=r;++i)
	if (f[l][i-1]) ret=(ret-1LL*f[l][i-1]*g[sum[r]-sum[i-1]]%mod+mod)%mod;
	f[l][r]=ret; (ans+=1LL*ret*g[n-(k<<1)-(sum[r]-sum[l-1])]%mod)%=mod;
}
int main()
{
	RI i,j; for (scanf("%d%d",&n,&k),n<<=1,i=1;i<=k;++i)
	scanf("%d%d",&x,&y),p[x]=y,p[y]=x;
	for (g[0]=1,i=2;i<=n;i+=2) g[i]=1LL*g[i-2]*(i-1)%mod;
	for (i=1;i<=n;++i) sum[i]=sum[i-1]+(!p[i]);
	for (j=1;j<=n;++j) for (i=n;i-j+1;--i) DP(i-j+1,i);
	return printf("%d",ans),0;
}


E - High Elements

妙不可言的一道题,确实佩服出题人的水平

首先一个直接的贪心想法就是逐位确定,那么我们需要判断对于已经确定的一个前缀状态,后面能否通过操作使其合法

我们记录当前两个序列的最大值(mx_0,mx_1)和前缀最大值个数(len_0,len_1)

对于剩下的数,我们相当于要安排两个序列(a_{1cdots x},b_{1cdots y})满足:

  • (mx_0<a_1<a_2<cdots < a_x)
  • (mx_1<b_1<b_2<cdots <b_y)
  • (x+len_0=y+len_1)
  • 剩下的数中,所有在原序列中的前缀最大值一定在序列(a)(b)

容易发现这些条件都是充要的,因为我们可以吧所有不是前缀最大值的数放在后面这样就不会产生贡献

那么我们可以把(a)(b)其中一个调整成全是原序列的前缀最大值,那么只需要确定另一个即可

我们先假定(a)全是原序列的前缀最大值,因此现在其实就是确定(b),然后把剩下不选的数都丢给(a)即可

假设剩下的数中剩下有(q)个数是原序列的前缀最大值,其中有(k)个被放到(b)里面去了,那么有:

[len_0+q-k=len_1+y ]

假设(b)中有(m)个数不是原来序列的前缀最大值,(y=k+m)带入并移项:

[2k+m=len_0-len_1+q ]

我们注意到右边是个定值,因此现在问题变成我们要对每个后缀找出一个上升子序列,每个位置上有个权值是(1)(不是原来序列的前缀最大值)或(2)(是原来序列的前缀最大值),要凑出某个权值

容易发现如果(c)可以被凑出来,那么(c-2)也能被凑出来,因此我们只需要分奇偶性维护满足LIS性质的最大权值即可

可以开两棵分别代表奇偶数的线段树来维护以上过程,总复杂度为(O(nlog n))

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,p[N],pmx,q,pos[2],len[2],f[N][2]; bool tp[N];
class Segment_Tree
{
	private:
		int mx[N<<2];
	public:
		#define TN CI now=1,CI l=1,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void modify(CI pos,CI mv,TN)
		{
			if (l==r) return (void)(mx[now]=mv); int mid=l+r>>1;
			if (pos<=mid) modify(pos,mv,LS); else modify(pos,mv,RS);
			mx[now]=max(mx[now<<1],mx[now<<1|1]);
		}
		inline int query(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return mx[now]; int mid=l+r>>1,ret=0;
			if (beg<=mid) ret=max(ret,query(beg,end,LS));
			if (end>mid) ret=max(ret,query(beg,end,RS)); return ret;
		}
		#undef TN
		#undef LS
		#undef RS
}T0,T1;
inline bool check(CI x,CI lim)
{
	if (lim<0) return 0; if (lim&1) return T1.query(x,n)>=lim; else return T0.query(x,n)>=lim;
}
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i)
	if (scanf("%d",&p[i]),pmx=max(pmx,p[i]),p[i]==pmx) ++q,tp[i]=1;
	for (i=n;i;--i)
	{
		int c0=T0.query(p[i],n),c1=T1.query(p[i],n);
		f[i][(c0+tp[i]+1)&1]=max(f[i][(c0+tp[i]+1)&1],c0+tp[i]+1);
		f[i][(c1+tp[i]+1)&1]=max(f[i][(c1+tp[i]+1)&1],c1+tp[i]+1);
		T0.modify(p[i],f[i][0]); T1.modify(p[i],f[i][1]);
	}
	if (!check(1,q)) return puts("-1"),0;
	for (i=1;i<=n;++i)
	{
		T0.modify(p[i],0); T1.modify(p[i],0); q-=tp[i];
		int x=p[i]>p[pos[0]]?i:pos[0],y=len[0]+(x==i);
		if (check(p[x],len[1]-y+q)||check(p[pos[1]],y-len[1]+q))
		pos[0]=x,len[0]=y,putchar('0'); else
		pos[1]=(p[i]>p[pos[1]]?i:pos[1]),len[1]+=pos[1]==i,putchar('1');
	}
	return 0;
}


Postscript

开始准备CSP初赛了,接下来到初赛应该都不会做AGC了

原文地址:https://www.cnblogs.com/cjjsb/p/13675333.html