AtCoder Grand Contest 029

Preface

这场D Before都好水的说,但是E思博想不出来啊

9/6:ABCDE solved,F看心情写

9/9:F solved,感性理解一下海星的说


A - Irreversible operation

这题我怀疑在后面的AGC中出现过类似的东西,显然变换操作就是把一个W左移

相当于统计一个逆序对即可

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,c; long long ans; char s[N];
int main()
{
	RI i; scanf("%s",s+1); n=strlen(s+1);
	for (i=1;i<=n;++i) if (s[i]=='B') ++c; else ans+=c;
	return printf("%lld",ans),0;
}


B - Powers of two

比较显然的贪心题。考虑我们枚举作为和的(2^i)

不难发现此时从大的数开始匹配一定不会更劣,随便开个multiset维护一下即可

#include<cstdio>
#include<vector>
#include<set>
#define RI  int
#define CI const int&
using namespace std;
int n,x,ans; multiset <int> s; vector <int> tp;
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&x),s.insert(x);
	for (i=(1<<30);i>1;i>>=1)
	{
		tp.clear(); while (s.size()>=2)
		{
			multiset <int>:: iterator it=s.end(); x=*(--it);
			if (x>=i) { s.erase(it); continue; } s.erase(it);
			if (s.count(i-x)) ++ans,s.erase(s.find(i-x)); else tp.push_back(x);
		}
		for (vector <int>:: iterator it=tp.begin();it!=tp.end();++it) s.insert(*it);
	}
	return printf("%d",ans),0;
}


C - Lexicographic constraints

首先一眼二分答案(x),考虑如何check

考虑显然第一个字符填满(1),考虑对于(iin [2,n])

  • (a_i>a_{i-1}),让(a_i)(a_{i-1})后面填满(1)即可
  • (a_ile a_{i-1}),将((a_i,a_{i-1}])清空,然后把第(a_i)位加(1)(可以把串视为一个(x)进制数)

容易发现我们可以直接开一个来维护所有非(1)的位置和值

注意特判(x=1)的情况

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,a[N],stk[N],c[N],top,ans;
inline bool check(CI lim)
{
	if (lim==1)
	{
		for (RI i=2;i<=n;++i) if (a[i]<=a[i-1]) return 0; return 1; 
	}
	top=0; for (RI i=2;i<=n;++i) if (a[i]<=a[i-1])
	{
		int x=a[i]; while (top&&stk[top]>x) --top;
		while (top&&stk[top]==x&&c[top]==lim) --top,--x;
		if (!x) return 0; if (stk[top]==x) ++c[top]; else stk[++top]=x,c[top]=2;
	}
	return 1;
}
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	int l=1,r=n,mid; while (l<=r)
	if (check(mid=l+r>>1)) ans=mid,r=mid-1; else l=mid+1;
	return printf("%d",ans),0;
}


D - Grid game

3min想2min写5min调的题,AGC竟然有这么水的D?

首先发现第一个人显然是需要一直向下走的,因此我们就是要找出他在第几行不能再向下走了

容易发现如果第二个人可以将他当前的纵坐标控制在一个区间([1,brd])

因此如果我们预处理出每一行最左边的障碍的纵坐标(mi_i),若(mi_{i+1}le brd)那么显然第二个人可以卡住他

否则第一个人一定可以向下走一格,然后若此时第二个人可以向右走就更新(brd)

总复杂度为(O(n))

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,m,q,brd,mi[N],x,y;
int main()
{
	RI i; for (scanf("%d%d%d",&n,&m,&q),i=1;i<=n;++i) mi[i]=m+1;
	for (i=1;i<=q;++i) scanf("%d%d",&x,&y),mi[x]=min(mi[x],y);
	for (brd=i=1;i<n;++i) if (mi[i+1]<=brd) return printf("%d",i),0;
	else if (brd+1<mi[i+1]) ++brd; return printf("%d",n),0;
}


E - Wandering TKHS

首先我们手玩一下样例发现显然所有到根路径上前缀最大值的点的答案都相同(因为前缀最大值的子树内的每个点一定会把子树内每个点都遍历一遍再从前缀最大值回到根)

我们考虑设一个(Q(v,x))表示对于(v)的子树内的所有点,只经过(<x)的点时能从(v)到达的点数

因此我们考虑对于一条边((v,fa_v))的答案会如何变化(设(mx_x)(fa_x)到根路径上的最大编号):

  • (v>mx_{fa_v}),此时(v)造成的新的贡献,(c_v-c_{fa_v}=Q(v,mx_v)+1)
  • (v<mx_{fa_v}),此时(v)仍然以(fa_v)为贡献,(c_v-c_{fa_v}=Q(v_,mx_v)-Q(v,mx_{fa_v}))

因此现在我们的任务就是快速计算出(Q(v,x)),然后好像网上有一个线段树合并的做法,但是实际上这道题有更加优美的(O(n))做法

我们发现(Q(v,x)=sum_c (1+Q(c,x)) ((c,v)in Eland c<x)),因此贡献的计算是递归的

而且因为我们对于一个(v)只需要对于(mx_v)(mx_{fa_v})算出答案,同时稍微推导一下就会发现在一条路径上会产生贡献的点最多只有(3)个)因此我们可以树上差分计算答案

具体地,我们对于上面的(v=mx_v)的情况直接大力搜索出单独的贡献,其他情况从子树累加来即可

具体实现看代码,复杂度(O(n))

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
struct edge
{
	int to,nxt;
}e[N<<1]; int n,head[N],cnt,x,y,dlt[N],mx[N],c[N],sz[N],ext[N];
inline void addedge(CI x,CI y)
{
	e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
	e[++cnt]=(edge){x,head[y]}; head[y]=cnt;
}
#define to e[i].to
inline void calc(CI now,CI fa,CI lim)
{
	ext[now]=1; for (RI i=head[now];i;i=e[i].nxt)
	if (to!=fa&&to<lim) calc(to,now,lim),ext[now]+=ext[to];
}
inline void DFS1(CI now=1,CI fa=0)
{
	sz[now]=1; mx[now]=max(mx[fa],now);
	for (RI i=head[now];i;i=e[i].nxt) if (to!=fa)
	DFS1(to,now),sz[now]+=sz[to],dlt[now]+=dlt[to];
	if (mx[now]==now) calc(now,fa,mx[fa]),dlt[now]=-sz[now];
	if (mx[fa]==fa) dlt[now]+=sz[now];
}
inline void DFS2(CI now=1,CI fa=0)
{
	if (fa)
	{
		if (mx[now]==now) c[now]+=ext[now]; else
		if (mx[fa]==fa) c[now]+=dlt[now]-ext[now]; c[now]+=c[fa];
	}
	for (RI i=head[now];i;i=e[i].nxt) if (to!=fa) DFS2(to,now);
}
#undef to
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<n;++i)
	scanf("%d%d",&x,&y),addedge(x,y);
	for (DFS1(),DFS2(),i=2;i<=n;++i) printf("%d ",c[i]);
	return 0;
}


F - Construction of a tree

神得一批的证明题,感觉每次充要条件我都只能理解必要性看不懂充分性(因此以下的证明都偏感性)……

首先我们考虑如果把这棵树变成有根树,先假定以(1)为根

然后我们现在考虑就是要在每个集合中选出一条边((fa_{x_i},x_i)),因为定下了树根,我们肯定可以按照深度的顺序把剩下的(n-1)条边加入

具体地,如果我们求出一组集合与其对应元素(除了(1))的完美匹配(没有显然是无解的),我们可以每次大力BFS找出一种构造方案

一个点可以加入树中当且仅当集合中已经有数出现在树中了,直接把这个数认定为当前的(fa)即可

考虑如果最后不能把所有点加入树中,说明必然存在某个时刻,使得未加入树的点的集合中不包含已经在树上的点

考虑我们如果换一个点作为根能否构造出有解的方案,考虑两种匹配直接的差别其实就是交换某些(x_i),我们分类讨论一下:

  • 被交换的(x_i)在之前已经在之前树的集合中了,显然不会产生有解的方案。因为前面的已经构造出一棵树了,所以没有影响
  • 被交换的(x_i)在后面没有加入树的集合中,显然也不会产生有解的方案。因为后面的集合的并集中不包含前面树内的任意一个点,而我们不可能在前后的两种集合之间交换(x_i)

因此我们现在知道了,以任意一个点为根不会影响答案,所以直接做即可

Dinic求二分图匹配,复杂度为(O(nsqrt m))(刚开始偷懒写了匈牙利被卡了一个点)

#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,INF=1e9;
struct edge
{
	int to,nxt,v;
}e[N<<2]; int n,x,y,cnt=1,head[N],frm[N],pre[N],q[N],s,t;
inline void addedge(CI x,CI y,CI z)
{
    e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
    e[++cnt]=(edge){x,head[y],0}; head[y]=cnt;
}
#define to e[i].to
namespace NF //Network Flow
{
    int cur[N],q[N],dep[N];
    inline bool BFS(CI s,CI t)
    {
        RI H=0,T=1; memset(dep,0,t+1<<2); q[dep[s]=1]=s;
        while (H<T)
        {
            int now=q[++H]; for (RI i=head[now];i;i=e[i].nxt)
            if (to!=1&&e[i].v&&!dep[to]) dep[to]=dep[now]+1,q[++T]=to;
        }
        return dep[t];
    }
    inline int DFS(CI now,CI tar,int dis)
    {
        if (now==tar) return dis; int ret=0;
        for (RI& i=cur[now];i&&dis;i=e[i].nxt)
        if (to!=1&&e[i].v&&dep[to]==dep[now]+1)
        {
            int dist=DFS(to,tar,min(dis,e[i].v));
            dis-=dist; ret+=dist; e[i].v-=dist; e[i^1].v+=dist;
        }
        if (!ret) dep[now]=0; return ret;
    }
    inline int Dinic(CI s,CI t,int ret=0)
    {
        while (BFS(s,t)) memcpy(cur,head,t+1<<2),ret+=DFS(s,t,INF);	return ret;
    }
};
int main()
{
	RI i,j; for (scanf("%d",&n),i=1;i<n;++i)
	for (scanf("%d",&x),j=1;j<=x;++j) scanf("%d",&y),addedge(y,n+i,1);
	for (s=0,t=n<<1,i=1;i<=n;++i) addedge(s,i,1); for (i=1;i<n;++i) addedge(n+i,t,1);
	if (NF::Dinic(s,t)!=n-1) return puts("-1"),0;
	for (j=2;j<=n;++j) for (i=head[j];i;i=e[i].nxt) if (to>n&&!e[i].v) frm[to-n]=j;
	RI H=0,T=1; q[1]=1; while (H<T)
	{
		int now=q[++H]; for (i=head[now];i;i=e[i].nxt)
		if (to>n&&!pre[to-n]) pre[to-n]=now,q[++T]=frm[to-n];
	}
	if (T!=n) return puts("-1"),0;
	for (i=1;i<n;++i) printf("%d %d
",pre[i],frm[i]);
	return 0;
}


Postscript

初赛要来了看来最近刷不了太多AGC了……

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