MX 暑假集训 7.10
【初音ミク】蜘蛛絲Monopoly【sasakure.UK】
神秘 dp 来袭。
T1: Awkward
题意
给定一棵 \(n\) 个节点的树,你需要求有多少个排列满足对于相邻两项都不为树上的一条边,答案对 \(10^9+7\) 取模。
\(2\le n\le 5\times 10^3\)。
solution
只会链的情况喵,不会排列计数喵。
考虑容斥,钦定一个边集 \(S \subseteq E\) 一定满足这些边在排列中都相邻,其他的边随意,令方案数为 \(F(S)\),则答案为 \(\sum (-1)^{\lvert S \rvert }F(S)\)。
注意到对于一个边集 \(S\) 一定不会使得一个点的度数大于 \(2\),因为若一个点度数大于 \(2\) 则会使得它需要相邻的点超过两个,不符合排列的需求。
这样的话一个边集就可以看作若干条链,而每条长度不为 \(1\) 的链上的方案数只有两个(正着放和反着放),将每个连通块缩成一个点,这些点之间可以任意排列,所以对于一个边集 \(S\),令 \(c(S)\) 为其形成长度不为 \(1\) 的链的数量,\(\lvert S \rvert =k\),则有 \(F(S)=2^{c(S)}(n-k)!\)。
那么令 \(A_k=\sum\limits_{\lvert S\rvert =k}2^{c(S)}\),答案就为 \(\sum\limits_{k=0}^{n-1} (-1)^{k}A_k(n-k)!\)。
考虑用个树形 dp 求出 \(A_k\),令 \(f_{i,j,k}\) 表示在第 \(i\) 个节点子树及其连向父亲的边中共选了 \(j\) 条,连向父亲的边是否被选上的每种方案权值之和。
我们将 \(2\) 倍的贡献统一在链顶加上。
我们先将儿子合并起来,令 \(g_{i,t}\) 表示考虑完若干子节点,其中共选了 \(i\) 条边,有 \(t\) 个子节点连向当前节点的所有方案权值和,这里的 \(i\) 条边不包括当前节点与父亲的连边,同时 \(0\le t\le 2\),因为这个点连边数量不超过 \(2\)。
对每个子节点 \(v\),\(g\) 的转移是:
处理完所有子节点后,\(g\) 对 \(f\) 的转移是:
这样就做完了,由树形背包的时间复杂度,两个节点仅在 lca 处贡献一次,所以时间复杂度是 \(O(n^2)\) 的。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e3+10;
constexpr int inf=1e9+10;
constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n;
int siz[N],f[N][N][2],g[N][N][3],tmp[N][3];
ll fac[N];
vector<int> e[N];
inline void dfs(int now) {
siz[now]=1;
g[now][0][0]=1;
for (int v:e[now]) {
dfs(v);
f(i,0,siz[now]) f(k,0,2) tmp[i][k]=g[now][i][k],g[now][i][k]=0;
f(i,0,siz[now]) f(j,0,siz[v]) {
add(g[now][i+j][0],1ll*tmp[i][0]*f[v][j][0]%mod);
add(g[now][i+j][1],(1ll*tmp[i][0]*f[v][j][1]+1ll*tmp[i][1]*f[v][j][0])%mod);
add(g[now][i+j][2],(1ll*tmp[i][1]*f[v][j][1]+1ll*tmp[i][2]*f[v][j][0])%mod);
}
siz[now]+=siz[v];
}
f(i,0,siz[now]) {
f[now][i][0]=(g[now][i][0]+2ll*g[now][i][1]+2ll*g[now][i][2])%mod;
if (i) f[now][i][1]=ad(g[now][i-1][0],g[now][i-1][1]);
}
}
int main() {
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
read(n);fac[0]=1;
f(i,1,n) fac[i]=fac[i-1]*i%mod;
f(i,2,n) {
int fa;read(fa);
e[fa].push_back(i);
}
dfs(1);
ll ans=0;
f(i,0,n-1) {
int op=((i&1)?-1:1);
ans+=op*f[1][i][0]*fac[n-i]%mod;
}
printf("%lld\n",(ans%mod+mod)%mod);
return 0;
}
T2: 字典树收益
题意
给定长度为 \(n\) 仅由 \(0,1,?\) 组成的字符串,你需要将其中的 \(?\) 填成 \(0\) 或 \(1\),然后再找出一个子序列,将这个子序列提取出来得到一个新字符串。
你需要将这个字符串分成若干个子串 \(s_1,s_2,s_3,\dots ,s_t\) 满足字典序单调递增,收益为将它们插入字典树后的节点数量,代价为 \(\sum\limits_{i=1}^{t}cost_{\lvert s_i\rvert }\),其中 \(cost\) 是给定的代价数组。
求出收益与代价之差的最大值。
\(1\le T\le 100,1\le n\le 200,1\le \sum n^3\le 5\times 10^7,0\le cost_i\le i\)。
solution
T3: 路径染色
题意
给定一棵 \(n\) 个节点的树,每个节点 \(i\) 有一个人。
你需要给每个人确定一个终点,然后按照你指定的顺序依次走向终点,第 \(i\) 个人会将经过的边都染成 \(c_i\) 的颜色(覆盖原本颜色),按顺序给出所有边,求每条边都被染色时按边的顺序得到边的颜色序列字典序最小值。
\(1\le n\le 10^5,1\le T\le 5\)。
solution
Hyperregular Bracket Strings
题意
给定 \(k\) 个 \([l_i,r_i]\subseteq [1,n]\),求出长度为 \(n\) 的括号序列满足对于所有子段 \([l_i,r_i]\) 都是合法括号序列的方案数,答案对 \(998244353\) 取模。
\(1\le n,k\le 3\times 10^5\)。
solution
这不是我们的 [ICPC 2015 WF] Tours 弱化版吗,五分钟秒了。
两个限制区间的交也必然是合法括号序列,稍微推一下可以看出随便选出若干限制区间进行各种交并补运算得到区间都必然是合法括号序列,也就是每个位置有覆盖它的限制区间的集合,将这个集合相同所有位置拿出来,它们必须是一个合法括号序列,且这个条件是充要的。
对每个限制区间异或上随机权值,做个异或哈希,最后得到的各个序列用卡特兰数乘起来即可。
可以做到 \(O(n+k)\)。
Code
#include<cstdio>
#include<algorithm>
#include<random>
#include<unordered_map>
using namespace std;
#define ll long long
#define ull unsigned long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=3e5+10;
constexpr int inf=1e9+10;
constexpr int mod=998244353;
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
inline ll qpow(ll a,int b) {
ll res=1;
while (b) {
if (b&1) res=res*a%mod;
a=a*a%mod;b>>=1;
}
return res;
}
int n,m;
ll C[N];
ull H[N];
mt19937 Ff(472130);
inline ull rnd() {
ull k1=Ff(),k2=Ff();
return (k1<<32)|k2;
}
inline void solve() {
read(n);read(m);
f(i,1,m) {
int l,r;read(l);read(r);
ull k=rnd();H[l]^=k;H[r+1]^=k;
}
unordered_map<ull,int> mp;
f(i,1,n) {
H[i]^=H[i-1];
mp[H[i]]++;
}
f(i,1,n+1) H[i]=0;
ll ans=1;
for (pair<ull,int> p:mp) {
if (p.second&1) ans=0;
else ans=ans*C[p.second>>1]%mod;
}
printf("%lld\n",ans);
}
int main() {
C[0]=1;
f(i,1,300000) C[i]=C[i-1]*(i*4-2)%mod*qpow(i+1,mod-2)%mod;
int T;
read(T);
while (T--) solve();
return 0;
}
Subset with Zero Sum
题意
给定一个长度为 \(n\) 的序列 \(a\) 满足 \(i-n\le a_i\le i-1\),找出一个子序列满足 \(a_i\) 之和为 \(0\),可以证明必然存在这样的子序列。
\(1\le n\le 10^6\)。
solution
神秘构造题。
我们发现这个 \(a_i\) 的值域非常的神秘,于是我们可以换一下位置,令 \(b_i=i-a_i\),则 \(1\le b_i\le n\)。
\(\sum a_i=\sum i-(i-a_i)=\sum i-\sum b_i\),于是我们需要找到子序列满足 \(\sum b_i=\sum i\) 即可。
每个 \(i\) 向 \(b_i\) 连边,最后形成一个基环森林,找到环即为答案。
时间复杂度 \(O(n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;
inline void read(int &x) {
x=0;int f=1;
char ch=getchar();
while (ch<48) {if(ch=='-')f=-1;ch=getchar();}
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}
int n;
int d[N];
vector<int> e[N],ans;
inline void topu() {
queue<int> q;
f(i,1,n) if (!d[i]) q.push(i);
while (!q.empty()) {
int now=q.front();
q.pop();
for (int v:e[now]) {
d[v]--;
if (!d[v]) q.push(v);
}
}
f(i,1,n) if (d[i]) ans.push_back(i);
}
inline void solve() {
read(n);
f(i,1,n) {
int x;read(x);
e[i].push_back(i-x);
d[i-x]++;
}
topu();
printf("%d\n",(int)(ans.size()));
for (int x:ans) printf("%d ",x);
putchar(10);
f(i,1,n) d[i]=0,e[i].clear();
ans.clear();
}
int main() {
int T;
read(T);
while (T--) solve();
return 0;
}

浙公网安备 33010602011771号