神秘做题记录4
P6378 [PA 2010] Riddle
发现限制有两种:
- 每条边的一个端点不选,另一个必须选。
- 每个集合内的一个点选了,其他点不能选
这个题给定了无向图,很难直接刻画dp之类的算法,考虑对这个限制进行图论建模。
\(\forall u,v(u,v之间有边),\neg u \Rightarrow v,\neg v \Rightarrow u\)
\(\forall u,v \in S,u \Rightarrow \neg v\)
经典的2SAT问题,跑tarjan,获得了92分。
错误是RE,发现第二种限制的边是 \(n^2\) 量级的,所以还不等TLE空间就不够了。
点的个数是 \(2e6\) 量级,线段树优化建图都做不了,必须要优化到 \(O(n)\) 量级。
发现第二种限制其实就是一个点连前缀和后缀。
于是类似线段树优化建图,使用“前缀和优化建图”,具体是怎么样呢?
看图就明白了,pre和suf其实是分开的,pre之间连边就构成了传递性,suf同理。
这样的点数翻倍,边也会多很多但是还是 \(O(n)\) 的比线段树优化建图更优。
#include<bits/stdc++.h>
using namespace std;
const int N = 4000010,M = 40000010;
int n,m,k;
int a[N],pre[N],suf[N];
struct Edge{int to,nxt;}e[M];
int head[N],idx = 0;
void addedge(int u,int v){
e[++idx] = {v , head[u]};
head[u] = idx;
}
int dfn[N],low[N],bel[N],inst[N];
int id = 0,scc_cnt = 0,cnt;
stack<int> st;
void tarjan(int x){
low[x] = dfn[x] = ++id;
inst[x] = 1;
st.push(x);
for(int i = head[x];i;i = e[i].nxt){
int h = e[i].to;
if(!dfn[h]){
tarjan(h);
low[x] = min(low[x] , low[h]);
}else if(inst[h]){
low[x] = min(low[x] , low[h]);
}
}
if(dfn[x] != low[x])return;
int y;
scc_cnt++;
do{
y = st.top();
st.pop();
bel[y] = scc_cnt;
inst[y] = 0;
}while(y != x);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>k;
for(int i = 1;i<=m;i++){
int u,v;
cin>>u>>v;
addedge(u + n , v);
addedge(v + n , u);
}
cnt = 2 * n;
for(int i = 1;i<=k;i++){
int sz;
cin>>sz;
for(int j = 1;j<=sz;j++){
cin>>a[j];
pre[j] = ++cnt,suf[j] = ++cnt;
addedge(a[j] , pre[j]);
addedge(suf[j] , a[j] + n);
}
for(int j = 1;j<sz;j++){
addedge(pre[j] , pre[j + 1]);
addedge(suf[j + 1] , suf[j]);
addedge(a[j + 1] , suf[j]);
addedge(pre[j] , a[j + 1] + n);
}
}
for(int i = 1;i<=n * 2;i++){
if(!dfn[i])tarjan(i);
}
bool fl = true;
for(int i = 1;i<=n;i++){
assert((bel[i] != 0) && (bel[i + n] != 0));
if(bel[i] == bel[i + n])fl = false;
}
if(fl)cout<<"TAK\n";
else cout<<"NIE\n";
return 0;
}
P6939 [ICPC 2017 WF] Tarot Sham Boast
我的题解
[题解:P6939 ICPC 2017 WF] Tarot Sham Boast - 洛谷专栏
P6478 [NOI Online #2 提高组] 游戏
发现问题转化为两个点配对,直接树背包设 \(dp_{i , j}\) 为以 \(i\) 为根的子树内配对 \(j\) 次的方案数。
转移好做,只要判断每个点的颜色,那么子树内另一个颜色的点剩几个好求。
我以为做完了去看题解确认一下,果然假了,要套二项式反演。
这个好像还挺显然,因为剩下的点也要配对,方案乘进去即可,但是不能保证剩下的配对没用祖先关系,所以反演一下。
这就做完了,它的削弱版 P4859 就是在链上的,更加简单,紫题 += 2。
P15649 [省选联考 2026] 找寻者
切了大部分,主要是卡常部分用了 GPT 提示。
因为半年前我想了三四个小时,所以一下就能切入题意,我发现重要的是子树的链多长。
于是设 \(dp_{i,j}\) 为 \(i\) 往下延伸的链长为 \(j\) 的概率,且不说转移,这个怎么计算答案?
我们考虑一条轻链的贡献,显然是 \(sz_v\) 对吧,这个就拆开了,我当时考场上连这个都不会。
所以知道是轻链的概率答案好求,知道是轻链的概率就免不了求这个dp。
考虑进行树背包,随便转移,设 \(f_i\) 表示当前节点儿子所有链总长为 \(i\) 的概率。
但是出现了问题。
设考虑一个儿子 \(h\) 时,你枚举 \(h\) 的链长 \(j\),再枚举一个 \(f_i\),但它是由很多个不同 \(j\) 转移来的。
这个很麻烦,也就是说需要一个不含 \(h\) 贡献的 \(f\),首先想的是拼起来,但是这个复杂度包错的,出个菊花就卡了。
然后想到夜跑,没错又是夜跑,神题来了,可撤销树背包,我忘了夜跑是怎样的了,所以重新想。
发现困难,这个转移并非 01背包式的往后推一下,而是一个二维的东西。
设 \(h\) 最小可能长度为 \(len\) ,设扣除后的 \(f\) 为 \(g\),那么 \(g_{len}\) 显然随便求了,然后把 \(g_{len}\) 更新的东西在 \(f\) 里减去。
这样 \(len + 1\) 也没有收到任何影响,\(g_{len + 1}\) 也可求,往后退就全有了。
过了之后看题解发现这个转移其实是多项式乘法,我的撤销其实是多项式除法,确实除法,跟高精除相似。
有了 \(g\) 以后直接计算 \(h\) 是重儿子的概率,不是重儿子就是轻儿子,所以答案肯定好求。
这个做法只有 \(64\) 分,我还是优化掉逆元的log才交的,实在没招了给GPT了,GPT非要说我没问题,让我特判有 \(0\) 就不乘。
判完过了?!非常神秘,这个要记住真的太有用了。
P15650 [省选联考 2026] 摩卡串
看到这个题我想用Trie刻画一下,发现要么是 \(s\) 的左链要么是 \(s\) 真前缀才能字典序更小。
这个没啥用,然后考虑了dp,设 \(dp_{i,j}\) 表示前 \(i\) 位有 \(j\) 个字串字典序小于 \(s\),怎么转移?参考扫描线。
发现还要存当前和 \(s\) 的匹配情况,即有哪些后缀是 \(s\) 的前缀,这个我只想到了状压于是还是 \(15\) 分,等同于赛时得分。
题解的dp思路类似,也是类数位dp。
首先说一个性质,就是假设 \(s\) 这位是 \(1\),你填 \(0\),他就变成左链,以后填啥都比 \(s\) 小,设这样的位置有 \(c\) 个,那么没填上一位都会有 \(c\) 的贡献。
设 \(dp_{p , c , cnt , fl}\) 表示当前匹配到 \(p\) 位,\(c\) 个左链,总共 \(cnt\) 个字典序小于 \(s\),出没出现过 \(s\)。
发现这个是匹配过程,会有失配,所以考虑kmp优化dp,设 \(to_{p,0/1}\) 表示 \(p\) 位后面填 \(0/1\) 会跳到哪里。
设 \(num_p\) 表示 \(p\) 往前跳有多少个填 \(0\) 能获得左链的位置,这个看似很难,但是考虑kmp的性质,左链一定是有一段匹配上的,只有根匹配上也算。
那么其实就是跳fail,跳完还能匹配上,我们看当前的 \(j + 1\) 位是否为 \(1\) 即可,注意根节点要判。
设 \(dep_p\) 为 \(p\) 跳fail次数,用这个维护 \(s\) 的前缀,\(p == n\) 需要 \(-1\) 因为不是前缀,题目要求严格小于。
这两个东西都可以kmp维护。
转移部分如下:
- \(p' = to_{p,i}\)
- \(c' = c + (i == 0) \times num_p\)
- \(cnt' = cnt + c'+ dep_{p'}\)
- \(fl' = fl | (p' == n)\)
这个很简单,但是我们发现并没有记录长度,那怎么办呢?
考虑常规数位 \(dp\),用的是dfs,一般为计数,这个题是最优化的数位dp,每填一个数花费 \(1\) 跳到下一个状态,求到一个合法状态的最小花费。
这个显然可以抽象成最短路模型,因为边权均为 \(1\),甚至可以直接上bfs,实现 \(O(1)\) 量级的转移因为一个点就两条出边。
这个就能做了,下一个问题是复杂度,这个一看像 \(nk^2\),绝对不可能通过。
但是看 \(c\) 的转移,这个你不去填 \(0\),那么 \(c\) 一定很小,如果填了 \(0\),那么会产生巨多无比的贡献,是根号量级的,所以复杂度 \(O(nk \sqrt k)\),可以通过。
最后就是要输出方案,这个记录每个状态从哪转移来就行,同最短路输出路径。
自此做完了省选所有 < 黑的题。

浙公网安备 33010602011771号