Atcoder Grand Contest 007&008

Shik and Travel

题目描述

点此看题

解法

首先上来二分答案 \(k\),然后变成判定性问题。

在树上走可以很容易联系到 \(dp\),发现我们要记录的信息是走到子树内第一个叶子的距离 \(a\),和从子树内最后一个叶子走回来的距离 \(b\),这样转移的时候就可以把左右子树拼起来。

\(f(u,a)\) 表示子树 \(u\) 内,\(a\) 对应最小的 \(b\) 是多少。转移时可以看成左右儿子有若干个二元组 \((a,b)\),设 \(x,y\) 为边权,我们这样考虑合并 \((a_1,b_1)\)\((a_2,b_2)\)(要做两次,可以左一右二,也可以左二右一):

  • 如果 \(x+b_1+y_1+a_2\leq k\),那么会产生二元组 \((a_1+x,b_2+y)\)

考虑扫描所有 \((a_1,b_1)\),那么我们取尽量大的 \(a_2\),就可以得到尽量小的 \(b_2+y\),显然这是一个双指针问题。

这样做的复杂度是多少呢?考虑状态数较少的那么为 \(sz\),那么它作为 \(1\) 的时候会产生恰好 \(sz\) 个状态,它作为 \(2\) 的时候只有 \(sz\) 个状态是会被保留的(\(a\leq a'\and b\leq b'\) 会排除掉 \((a',b')\)),所以在 \(u\) 上产生了 \(2\cdot sz\) 个状态。并且我们可以如果一个状态被扫描过一次就会被抛弃,所以复杂度就是新增状态总数,可以得到复杂度是 \(O(n\log n)\) 的。

总时间复杂度 \(O(n\log n\log a)\),我的代码中偷懒用了排序所以多了一个 \(\log\)

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
#define int long long
#define pii pair<int,int>
#define pb push_back
#define x first
#define y second
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,ans,ch[M][2],c[M][2];vector<pii> v[M];
void dfs(int u)
{
	v[u].clear();
	if(!ch[u][0]) {v[u].pb({0,0});return ;}
	dfs(ch[u][0]);dfs(ch[u][1]);
	vector<pii> vc;
	for(int d=0;d<2;d++)
	{
		int ls=ch[u][d],rs=ch[u][d^1];
		int lc=c[u][d],rc=c[u][d^1],tmp=k-lc-rc;
		for(int i=0,j=0;i<v[ls].size();i++)
		{
			while(j+1<v[rs].size() && v[rs][j+1].x+v[ls][i].y<=tmp)
				j++;
			if(j>=v[rs].size() || v[rs][j].x+v[ls][i].y>tmp) continue;
			vc.pb({v[ls][i].x+lc,v[rs][j].y+rc});
		}
	}
	sort(vc.begin(),vc.end());
	for(auto t:vc)
	{
		if(!v[u].empty() && v[u].back().y<=t.y) continue;
		v[u].pb(t);
	}
}
signed main()
{
	n=read();
	for(int i=2;i<=n;i++)
	{
		int j=read(),w=ch[j][0]>0;
		ch[j][w]=i;c[j][w]=read();
	}
	int l=0,r=1e12;
	while(l<=r)
	{
		k=(l+r)>>1;dfs(1);
		if(v[1].empty()) l=k+1;
		else r=k-1,ans=k;
	}
	printf("%lld\n",ans);
}

Next or Nextnext

题目描述

点此看题

解法

做这道题真的太难受了,理解起来困难,代码细节写错,不知道花了多少时间\(...\)

首先把题目限制做一个转化,我们使用经典建图 \(i\rightarrow p_i\),那么建出来一定是若干个环,并且每个点在环上跳一步或者跳两步可以到达 \(a_i\),我们称之为答案图

为了更充分的思考 \(a_i\) 的限制我们建出限制图,也就是 \(i\rightarrow a_i\) 的图,我们综合思考这两个图。考虑限制图是一棵基环内向树,但是如果我们从答案图的角度去构建限制图,每条边在环上的距离都 \(\leq 2\),所以只有这些情况:

  • 如果答案图的长度是偶数,那么限制图可以原封不动,或者分裂成两个长度相等的环(都跳两步)
  • 如果答案图的长度是奇数,那么限制图可以原封不动,或者变成另一个环(都跳两步,非自环)
  • 有可能把限制图构建成无分岔的基环内向树(因为只能跳一步或者两步,所以一旦有分叉环就接不上)

因为现在我们只知道限制图,所以我们通过上述情况还原答案图来计数:

  • 对于不存在支链的环:可以把两个长度相同的环拼起来;可以单独还原一个环,如果环是奇数且非自环,则有单独弄有两种情况;否则只能原封不动。这里可以通过简单 \(dp\) 实现(决策是拼起来还是自己搞)
  • 对于存在支链的环,可以看下图:

在这里插入图片描述

\(lspace\) 表示上一个支链的根到这个支链的根的距离(特别地,如果只有一个支链那么 \(lspace=n\)),\(ltree\) 表示这个支链的长度,两种情况分别是在根前面放支链的节点或者是放环上的节点,那么方案数是 \([lspace\geq ltree]+[lspace>ltree]\),判断方案可不可行的方法就是看够不够把支链塞到环里面。

总结

双图策略可以帮助你充分考虑已知信息和所求,通过思考图的关系来发现问题性质。

#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
const int M = 100005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,tot,ans,f[M],a[M],b[M],c[M],vis[M],dep[M];
struct edge{int v,next;}e[M];
void dfs(int u)
{
	dep[u]=1;int ls=0;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(vis[v]==2) continue;
		if(ls) {puts("0");exit(0);}
		ls=1;dfs(v);dep[u]=dep[v]+1;
	}
}
signed main()
{
	n=read();ans=1;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();//i->a[i]
		e[++tot]=edge{i,f[a[i]]},f[a[i]]=tot;//a[i]->i
	}
	for(int i=1;i<=n;i++) if(!dep[i])
	{
		int t=0,x=i,lst=1;
		for(;!vis[x];x=a[x]) vis[x]=1;
		for(;vis[x]==1;x=a[x]) vis[x]=2,c[++t]=x;
		for(int j=t;j>=1;j--)
		{
			dfs(c[j]);
			if(lst==1 && dep[c[j]]!=1) lst=j-t;
		}
		if(lst==1) {b[t]++;continue;}
		for(int j=1;j<=t;j++) if(dep[c[j]]>1)
		{
			int ls=j-lst,lt=dep[c[j]]-1;
			if(ls<lt) {puts("0");exit(0);}
			if(ls>lt) ans=ans*2%MOD;
			lst=j;
		}
	}
	for(int i=1;i<=n;i++) if(b[i])
	{
		int lst=0,now=1,nxt=0;
		for(int j=1;j<=b[i];j++)
		{
			nxt=((i&1)&&i!=1)?(now<<1):now;
			nxt=(nxt+(j-1)*lst%MOD*i)%MOD;
			lst=now;now=nxt;
		}
		ans=ans*now%MOD;
	}
	printf("%lld\n",ans);
}

Black Radius

题目描述

点此看题

解法

还是要多动笔,这样题解才看懂得快,效率高 (☞゚ヮ゚)☞

\(f(x,d)\) 表示以 \(x\) 为圆心,\(d\) 为半径的点集。首先我们不考虑全部点都被染黑的情况,那么对于一个点集,得到它并且使得半径 \(d\) 最小的点 \(u\) 是固定的。这说明如果有多个 \(f(x,d)\) 表示的点集相同,我们在使得 \(d\) 最小的 \(x\) 处统计一次贡献就不会算重。

考虑简化问题:所有点都可以作为起点。那么我们考虑 \(d\) 最小的要求是:

  • 不能覆盖完所有点,设 \(mx[x]\) 为从 \(x\) 开始的最长链,则:\(d\leq mx[x]-1\)
  • 对于任意邻接点 \(y\),需要满足:\(f(x,d)\not=f(y,d-1)\),设 \(se[x]\) 表示不考虑 \(y\) 方向的最长链,那么:\(d-2<se[x]\),因为如果 \(d-2\geq se[x]\) 那么 \(y\) 可以把 \(x\) 方向的点覆盖完,而它们在 \(y\) 方向的覆盖范围相同,所以这样整体覆盖范围就相同了,不合法。

回到本题,我们把可以作为起点的点称为关键点。我们考虑把非关键点的贡献迁移到关键点上,也就是如果 \(f(x,d_1)\) 可以让 \(d\) 最小,但是 \(x\) 不是关键点,如果可以找到 \(f(y,d_2)=f(x,d_1)\) 并且 \(y\) 是关键点,那么它还是可以产生贡献。

结论:若 \(f(x,d_1)\) 使得 \(d_1\) 最小并且可以找到 \(f(x,d_1)=f(y,d_2)\),等价于 \(f(x,d_1)\) 覆盖完 \(y\) 方向的子树(注意这里并不是 \(y\) 的子树,而是连在 \(x\) 上的对应方向的子树)

首先证明充分性,设 \(v\)\(y\) 方向上的第一个点,因为 \(f(x,d_1)\not=f(v,d_1-1)\),而因为非 \(y\) 方向节点它们的覆盖范围相同,所以 \(f(y,d_2)\)\(v\) 的角度看还剩下的覆盖范围是 \(\geq d_1\) 的,它的覆盖范围大于 \(f(x,d_1)\)\(d_1-1\),而它们在 \(y\) 方向的覆盖方向相同,所以它们一定覆盖完了 \(y\) 方向的子树。

必要性也是容易证明的,显然这样的 \(d_2\) 是可以找到的。

应用上述结论,对于非关键点的贡献,\(d\) 的下界应该是存在关键点子树的最长链的最小值,所以 \(d\) 的合法取值范围一定是区间,我们用换根 \(dp\) 可以轻易地求出它。

//You may miss me deep down
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,tot,f[M],mx[M],se[M],d[M],sz[M];char s[M]; 
struct edge{int v,next;}e[M<<1];long long ans;
void dfs1(int u,int fa)
{
	if(s[u]=='1') d[u]=0,sz[u]=1;
	else d[u]=inf;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs1(v,u);sz[u]+=sz[v];
		if(mx[v]+1>mx[u]) se[u]=mx[u],mx[u]=mx[v]+1;
		else if(mx[v]+1>se[u]) se[u]=mx[v]+1;
		if(sz[v]) d[u]=min(d[u],mx[v]+1);
	}
}
void dfs2(int u,int fa)
{
	int up=min(se[u]+1,mx[u]-1);
	if(d[u]<=up) ans+=up-d[u]+1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		int t=(mx[v]+1==mx[u])?se[u]:mx[u];t++;
		if(t>mx[v]) se[v]=mx[v],mx[v]=t;
		else if(t>se[v]) se[v]=t;
		if(sz[1]-sz[v]) d[v]=min(d[v],t);
		dfs2(v,u);
	}
}
signed main()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,f[u]},f[u]=tot;
		e[++tot]=edge{u,f[v]},f[v]=tot;
	}
	scanf("%s",s+1);
	dfs1(1,0);dfs2(1,0);
	printf("%lld\n",ans+1);
}
posted @ 2022-02-06 17:03  C202044zxy  阅读(148)  评论(0编辑  收藏  举报