E - Two Houses

Description

有一个 \(n\) 个点的竞赛图,其中 \(i\) 的入度为 \(k_i\),你可以进行交互,每次询问点对 \((A,B)\),回答是否存在 \(A\)\(B\) 的路径,如果回答为 Yes 则不能再询问,否则可以继续询问。你需要找到 \(|k_x-k_y|\) 最大的一对点 \((x,y)\),且满足它们可以相互到达。

\(3\le n\le 500\).

Solution

好笑,我甚至不知道竞赛图是啥

结论 1:竞赛图强连通缩点后的强连通分量可以连成一条链,前面点向后面点连边。

具体证明在 第一个链接。你还会发现它们的拓扑序都是互异的。

结论 2:对于强连通分量 \(s,t\),若 \(s\) 的拓扑序小于 \(t\) 的拓扑序,那么对于 \(\forall x\in s,\forall y\in t\) 都有 \(\text{ind}_x<\text{ind}_y\).

结论 1 可得拓扑序在 \(t\) 之前的强连通分量中的点均向 \(y\) 连边,而 \(x\) 的入度取最大时也不过是拓扑序在 \(s\) 之前的强连通分量中的点和在 \(s\) 中的其它点向它连边。故结论得证。

结论 3:\(x,y\) 满足 \(\text{ind}_x<\text{ind}_y\),那么 \(x\) 可以到达 \(y\).

分类讨论。

  1. \(x,y\) 不属于同一个强连通分量。显然。
  2. \(x,y\) 属于同一个强连通分量。显然。

结论 3 我们可以直接得出入度小的点可以到达入度大的点,再询问入度大的点是否可以到达入度小的点,就可以判断 \((x,y)\) 能否相互到达了。

这样就有一个 \(\mathcal O(n^2)\) 的做法:枚举所有点对,将它们的入度差从大到小进行排序再依次询问。显然回答为 Yes 时也没必要询问下去了。

完了吗?

离谱的是,这题不仅可以 \(\mathcal O(n)\) 做,而且根本不需要查询!

结论 4:将点按入度进行排序,则同一个强连通分量的点是连续的。

结论 2 易得。

结论 5:将点按入度进行排序,对于 第一个 满足 \(\text{C}(i,2)=\sum_{j=1}^i \text{ind}_j\)\(i\),区间 \([1,i]\) 构成一个强连通分量。

首先不管怎样有 \(\text{C}(i,2)\le\sum_{j=1}^i \text{ind}_j\),因为在 \([1,i]\) 之间就有 \(\text{C}(i,2)\) 条边。那么满足条件就意味着外部没有连向 \([1,i]\) 的边,所以外部的点不可能和内部的点形成强连通分量。

所以 \([1,i]\) 可能形成一个或多个强连通分量。但真的可能有多个吗?假设 \([1,x],[x+1,i]\) 形成两个强连通分量,那么一定有外部点 \(y\)\([1,x]\) 中某点 \(z\) 连边,那么就一定形成了环,所以矛盾。

结论 6:将点按入度进行排序,如果到前 \(i\) 个点的入度和恰好为 \(\text{C}(i,2)\) 则出现了一个新的强连通分量。假设上一次符合条件的点是 \(\text{lst}\),则 \((\text{lst},i]\) 构成了一个新的强连通分量。

结论 5 可知,只要证明满足条件时有 \(\text{C}(i-\text{lst},2)=\sum_{j=\text{lst}+1}^i \text{ind}'_j\) 即可证明此结论,其中 \(\text{ind}'_j\) 是只看端点在 \((\text{lst},i]\) 之间的边,点 \(j\) 的入度。

考虑满足条件的 \(i\),假设把 \([1,i]\) 顺次分割成大小为 \(a_1,a_2,...,a_m\) 的强连通分量,最后的一段区间长度为 \(t\)(现在还不知道最后一段区间能否组成 \(\rm scc\))。那么最后一段区间的入度和可以被这样表示

\[\binom{t+\sum a_i}{2}-\sum_{i=1}^m \binom{a_i}{2}-\text{tmp} \]

大体思想是用 \([1,i]\) 中的所有入度减去每个之前的 \(\rm scc\)\(\text{ind}'\) 和,再减去两两 \(\rm scc\) 导致的入度,就是最后一段区间的 \(\text{ind}'\) 和。同时这个柿子也可以从组合意义上理解,容易得出就是 \(\binom{t}{2}\).

所以按入度升序排序,找到同一个 \(\rm scc\) 中隔得最远的两点,取 \(\max\) 即可。

Code

#include <bits/stdc++.h>
using namespace std;

#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y) 
#define debug(...) do {cerr<<__LINE__<<" : ("#__VA_ARGS__<<") = "; Out(__VA_ARGS__); cerr<<flush;} while(0)
template <typename T> void Out(T x) {cerr<<x<<"\n";}
template <typename T,typename ...I> void Out(T x,I ...NEXT) {cerr<<x<<", "; Out(NEXT...);}

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}

typedef pair <int,int> pii;

const int maxn=505;

int n;
pii s[maxn];
vector <int> buc[maxn];

int main() {
	int cnt=0,lst=0,sum=0,ans=-1,l=0,r=0;
	n=read(9);
	rep(i,1,n) buc[read(9)].push_back(i);
	rep(i,0,n) for(int j=0;j<buc[i].size();++j) s[++cnt]=make_pair(i,j);
	rep(i,1,cnt) {
		sum+=s[i].first;
		if(sum==i*(i-1)/2) {
			if(lst^(i-1)) {
				if(ans<s[i].first-s[lst+1].first) {
					ans=s[i].first-s[lst+1].first;
					l=buc[s[i].first][s[i].second],r=buc[s[lst+1].first][s[lst+1].second];
				}
			}
			lst=i;
		}
	}
	printf("! %d %d\n",l,r); fflush(stdout);
	return 0;
}

F - Christmas Game

Solution

不妨先考虑固定根的情况,容易发现可以将深度取模 \(K\) 的余数进行分类,分成若干个独立子游戏,最终的答案就是子游戏的 \(\rm sg\) 值的异或。

事实上,对于同余数的部分,就转化成了阶梯博弈的模型 —— 由于从偶阶梯开始操作最后一定回到原先的状态,所以将数字从奇阶梯转到偶阶梯相当于 "将石子扔进垃圾桶里"!

于是记 \(dp(u,i,0/1)\) 为以 \(u\) 为根,满足取模 \(K\)\(i\) 的节点 \(v\),且 \(v\) 相对于 \(i\) 是偶/奇阶梯的 \(v\) 的权值异或和。复杂度 \(\mathcal O(nK)\),换根时利用异或的性质即可。

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <vector>
using namespace std;

const int maxn = 1e5+5;

vector <int> e[maxn];
int n,K,a[maxn],dp[maxn][20][2],tmp[20][2];

inline void trans(int f[20][2],int g[20][2]) {
	for(int i=0;i<K;++i)
		for(int j=0;j<2;++j) {
			int ni=i+1, nj=j;
			if(ni==K) ni=0, nj^=1;
			f[ni][nj] ^= g[i][j];
		}
}

void dfs_1(int u,int fa) {
	dp[u][0][0]=a[u];
	for(const auto& v:e[u]) if(v^fa) {
		dfs_1(v,u); trans(dp[u],dp[v]);
	}
}

void dfs_2(int u,int fa) {
	if(u^1) {
		for(int i=0;i<K;++i) for(int j=0;j<2;++j) tmp[i][j]=dp[fa][i][j];
		trans(tmp,dp[u]), trans(dp[u],tmp);
	}
	for(const auto& v:e[u]) if(v^fa) dfs_2(v,u); 
}

int main() {
	n=read(9), K=read(9);
	for(int i=1;i<n;++i) {
		int u=read(9), v=read(9);
		e[u].emplace_back(v),
		e[v].emplace_back(u);
	}
	for(int i=1;i<=n;++i) a[i]=read(9);
	dfs_1(1,0), dfs_2(1,0);
	for(int i=1;i<=n;++i) {
		int ans=0;
		for(int j=0;j<K;++j) ans^=dp[i][j][1];
		printf("%d ",ans? 1: 0);
	} puts("");
	return 0;
}
posted on 2021-06-12 23:07  Oxide  阅读(41)  评论(0编辑  收藏  举报