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\).
分类讨论。
- \(x,y\) 不属于同一个强连通分量。显然。
- \(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\))。那么最后一段区间的入度和可以被这样表示
大体思想是用 \([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;
}