Educational Codeforces Round 130
D. Guess The String
比赛的时候一直在想分治,然后就寄了。首先 \(26\) 次询问 \(1\) 直接提示询问每个不同的字符,问题是那 \(6000\) 次询问 \(2\) —— 由于 \(\log 26<5\),所以这个复杂度实际上是 \(n\cdot \left(\log |\sum|+1\right)\) 的!
首先询问字符串的所有前缀,当不同字符数变化时,询问这个点的字母,反之,二分出在这个位置之前第一个和这个位置的字符相同的字符。为了使二分次数降低至 \(5\),可以只维护 \(26\) 个字符离此位置最近的字符位置,注意二分后要进行更新。
E. Coloring
根据题设条件可以发现,对于染同一颜色的点集,它们一定两两距离相等且为最小距离,其中一个点的最小距离定义为离这个点最近的点与其的距离。所以先 \(\rm dfs\) 出所有可以染同一颜色的点集,显然它们两两不交,那么对于一个点集内的点,要么染一种颜色,要么所有点颜色都不同。于是直接 \(\mathtt{dp}\) 即可。
F. Too Many Constraints
已知:这是一道 \(\text{2-sat}\) 题。我真的不太能想到这题的 motivation.
先考虑 \(a_i\ne x\) 的限制,显然不能给每个点建 \(k\) 个变量,代表 \(a_i\) 是否取 \(x\),因为这是一个 \(k\text{-sat}\) 问题 ouo。我们把一个点拆得多些 —— 对于 \(a_i\),有 \(V_{i,j,0/1}\) 中 \(0\) 表示 \(a_i>j\) 成立,\(1\) 表示 \(a_i\leqslant j\) 即其逆命题成立。
于是 \(a_i\ne x\) 就相当于 \(a_i>x\) 或 \(a_i\leqslant x-1\) 成立。其它情况也是容易推导的。复杂度 \(\mathcal O(k\cdot (n+m))\).
特别需要注意边界情况:
- 由于有 \(a_i>x\),所以 \(x\) 的取值范围需要扩张到 \([0,V]\);
- 可以发现,\(a_i\leqslant 0\) 与 \(a_i>V\) 都是不合法的,同时我们发现,当 \(a_i\leqslant 0\) 满足时,\(a_1\leqslant 0\) 一定满足,所以实际上只需要将 \(V_{1,0,1}\) 向它的逆连边即可,\(a_i>V\) 同理。
最后再特别提一下选取变量的问题:
记 \(\text{bel}(i)\) 为 \(i\) 所在强连通分量的拓扑序编号(由于我写的是 \(\rm tarjan\),所以编号小的拓扑序更大),如果 \(\text{bel}(i)<\text{bel}(i')\) 则选取 \(i\),否则选取 \(i\) 的逆即 \(i'\).
要证明上述构造是正确的,我们只需要证明所有被选取点不能到达没有被选取的点。使用反证法,对于被选取点 \(x\) 和未被选取点 \(y\),假定存在 \(x\rightarrow y\) 的路径,那么有 \(\text{bel}(x)\geqslant \text{bel}(y)\)。同时根据是否被选取我们可以知道 \(\text{bel}(x)<\text{bel}(x'),\text{bel}(y)>\text{bel}(y')\),所以可以推出 \(\text{bel}(x')>\text{bel}(x)\geqslant \text{bel}(y)>\text{bel}(y')\).
但是 根据对称性,这里存在一条 \(y'\rightarrow x'\) 的路径,即 \(\text{bel}(y')\geqslant \text{bel}(x')\),矛盾。
最后的最后,如果存在 \(x\rightarrow y\) 的限制,且 \(y\) 是不合条件的 必须进行 连边!
AtCoder Beginner Contest 209
E - Shiritori
博主的 \(\rm bibi\) 时间
当大家都在随切 \(\rm arc\) 的时候,我 \(\rm abc\) 都做不来 😭
Solution
设每个串前三位为 \(u\),后三位为 \(v\). 先开始想的是从 \(u\) 向 \(v\) 连边,但是判 Draw
即判环会很难。因为环上某点可能有非 Draw
解。所以反着建边跑拓扑排序,就可以将所有值初始为 Draw
,规避掉这个问题。
Code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define print(x,y) write(x),putchar(y)
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);
}
const int maxn=4e5+5;
int n,len,idx,in[maxn],ans[maxn];
char s[maxn][10];
map <string,int> mp;
vector <int> e[maxn];
queue <int> q;
void Go() {
rep(i,1,idx) {
ans[i]=-1;
if(!in[i]) q.push(i),ans[i]=0;
}
while(!q.empty()) {
int t=q.front(); q.pop();
for(int i=0;i<e[t].size();++i) {
int v=e[t][i];
if(ans[v]==-1) {
--in[v];
if(!ans[t]) ans[v]=1,q.push(v);
else if(!in[v]) ans[v]=0,q.push(v);
}
}
}
}
int main() {
string u,v; int x,y;
n=read(9);
rep(i,1,n) {
scanf("%s",s[i]+1); len=strlen(s[i]+1);
u=""; u+=s[i][1];
u+=s[i][2],u+=s[i][3];
v=""; v+=s[i][len-2];
v+=s[i][len-1],v+=s[i][len];
if(!mp[u]) mp[u]=++idx;
if(!mp[v]) mp[v]=++idx;
x=mp[u],y=mp[v];
e[y].push_back(x); ++in[x];
}
Go();
rep(i,1,n) {
len=strlen(s[i]+1);
v=""; v+=s[i][len-2];
v+=s[i][len-1],v+=s[i][len];
y=mp[v];
if(ans[y]==-1) puts("Draw");
else if(ans[y]) puts("Aoki");
else puts("Takahashi");
}
return 0;
}
F - Deforestation
Solution
尝试将问题简单化,先考虑树 \(i\) 和 \(i+1\).
容易发现,这两棵树被砍的顺序并不影响除它们之外树的贡献(感觉这种观察很常见,它是之后贪心的基础)。而先砍 \(i\) 贡献为 \(h_i+2h_{i+1}\),先砍 \(i+1\) 贡献为 \(2h_i+h_{i+1}\).
为了使花费最小化,我们应该先砍 \(h_i,h_{i+1}\) 中较高的一棵。
设 \(dp_{i,j}\) 为前 \(i\) 棵树,第 \(i\) 棵树是第 \(j\) 个被砍的砍伐排列数。
-
\(h_i=h_{i+1}\):\(dp_{i+1,j}=\sum_{k=1}^{i}dp_{i,k}\). 相当于没有限制,但由于只砍了前 \(i\) 棵树,所以 \(k\le i\);
-
\(h_i>h_{i+1}\):\(dp_{i+1,j}=\sum_{k=1}^{j-1} dp_{i,k}\);
-
\(h_i<h_{i+1}\):\(dp_{i+1,j}=\sum_{k=j}^i dp_{i,k}\). 取 \(dp_{i,j}\) 是因为在取 \(i\) 之前取了 \(j-1\) 棵树,\(i+1\) 正好可以插入到第 \(j\) 位,排在 \(i\) 之前。
用前缀和即可优化到 \(\mathcal O(n^2)\).
一个有趣的现象:自己在实现时将 '<'
打反了,但还是通过了此题。其实 "矮优先" 和 "高优先" 是等价的,可以把砍伐序列倒过来看来理解。
Code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define print(x,y) write(x),putchar(y)
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);
}
const int maxn=4005,mod=1e9+7;
int n,h[maxn],dp[2][maxn];
int main() {
n=read(9);
rep(i,1,n) h[i]=read(9);
dp[1][1]=1;
rep(i,2,n) rep(j,1,i) {
if(h[i]==h[i-1]) dp[i&1][j]=dp[i&1^1][i-1];
else if(h[i]<h[i-1]) dp[i&1][j]=dp[i&1^1][j-1];
else dp[i&1][j]=(dp[i&1^1][i-1]-dp[i&1^1][j-1]+mod)%mod;
dp[i&1][j]=(dp[i&1][j]+dp[i&1][j-1])%mod;
}
print(dp[n&1][n],'\n');
return 0;
}