G - 01Sequence
Description
你需要构造一个长度为 \(n\) 的只由 0 和 1 组成的序列,满足 \(m\) 个约束条件 \((l,r,x)\),表示 \(l\) 到 \(r\) 中至少有 \(x\) 个 1,如果有多种,则输出 1 的数量最小的那种。
\(n\leqslant 2\cdot 10^5\).
Solution
这题看上去就很差分约束。于是设 \(x_i\) 为序列中 \([1,i]\) 之间 1 的个数,那么 \((l,r,x)\) 就可以转化成 \(x_r-x_{l-1}\geqslant x\).
由于我们需要求 \(x_n\) 的最小值,可以从 \(l-1\) 向 \(r\) 连一条权值为 \(x\) 的边,求最长路。边权为正,所以不能用 \(\text{dijkstra}\),但这题用 \(\text{spfa}\) 会被卡,那咋办捏?
事实上与普通差分约束系统不同的是,区间总和是固定的,于是可以换一个角度:设 \(x_i\) 为序列中 \([1,i]\) 之间 0 的个数。那么 \((l,r,x)\) 就可以转化成 \(x_r-x_{l-1}\leqslant r-l+1-x\)。此时正好要求 \(x_n\) 的最大值,就转化成了从 \(l-1\) 向 \(r\) 连一条权值为 \(r-l+1-x\) 的边,求最短路。
另外还要注意一个限制:\(0\leqslant x_{i+1}-x_i\leqslant 1\).
Code
#include <cstdio>
#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((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <queue>
#include <cstring>
using namespace std;
const int maxn=2e5+5;
struct node {
int nxt,to,val;
} e[maxn*3];
struct Node {
int x,v;
bool operator < (const Node &t) const {
return v>t.v;
}
};
int n,m,cnt,head[maxn];
int d[maxn];
bool vis[maxn];
priority_queue <Node> q;
void addEdge(int u,int v,int w) {
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].val=w;
head[u]=cnt;
}
void Dijkstra() {
q.push((Node){0,0});
memset(d,0x3f,sizeof d);
d[0]=0;
while(!q.empty()) {
Node t=q.top(); q.pop();
if(vis[t.x] or (t.v^d[t.x]))
continue;
vis[t.x]=1;
for(int i=head[t.x];i;i=e[i].nxt) {
int v=e[i].to;
if(d[v]>d[t.x]+e[i].val) {
d[v]=d[t.x]+e[i].val;
q.push((Node){v,d[v]});
}
}
}
}
int main() {
n=read(9),m=read(9);
int l,r,x;
for(int i=1;i<=m;++i) {
l=read(9),r=read(9);
x=r-l+1-read(9);
addEdge(l-1,r,x);
}
for(int i=0;i<n;++i)
addEdge(i+1,i,0),
addEdge(i,i+1,1);
Dijkstra();
for(int i=1;i<=n;++i)
putchar(d[i]-d[i-1]^'1'),putchar(' ');
puts("");
return 0;
}
H - Random Robots
Description
给定 \(k\) 个机器人在数轴上的初始位置,有 \(n\) 个时刻,每个时刻的每个机器人都有一半概率不动或向正方向走一格,每个时刻所有机器人的移动是同时进行的。
求所有机器人始终不重合的概率,输出取模 \(998244353\) 意义下的值。
\(2 \leqslant k \leqslant 10 ,n \leqslant 1000 , 0 \leqslant x_1< x_2 < \ldots < x_k \leqslant 1000\).
Solution
一些闲话:咕了一年了这题,现在看来咕咕咕的题是不可能补完了 🥺。
再探 \(\text{LGV Lemma}\):
之前在 \(\text{[NOI 2021] }\)路径交点 一题中仓促地学了 \(\text{LGV Lemma}\),现在回来看发现还是有挺多没搞懂的,所以再写一写。再次抛出公式
\[\sum_{S:A→B}(−1)^{N(σ(S))}\prod_{i=1}^n ω(S_i) \]其中 \(S\text{:}A\to B\) 是一个双射。
事实上,\(\text{LGV Lemma}\) 最经典的应用是在求 平面图不相交路径数 中而非上题那个奇怪的形式。接下来我们解释为什么。平面图最典型的性质就是 边本身不相交,更进一步,也就是如果 \(N(\sigma(S))\ne 0\),就一定存在点相交。此命题的逆否命题同样成立,也就是如果不存在点相交,那么 \(N(\sigma(S))=0\),也就是说,不存在点相交的路径数对答案贡献的系数一定为 \(1\)。另外地,回顾上题中 "路径不交不影响" 部分的证明,可以证明点相交的情况一定可以被抵消。于是原命题得证。
再回到开始的问题,由于上题并未保证图是平面图,所以不存在 "点相交的路径数对答案贡献的系数一定为 \(1\)" 的结论,所以应用 \(\text{LGV Lemma}\) 只能用那个比较鬼畜的形式。
看到这里有没有感觉被骗了,因为上面的内容实际上和 \(\text{LGV Lemma}\) 没啥关系。
回到原问题,记 \((i,j)\) 为 \(i\) 时刻在位置 \(j\) 的某个状态,建图显然 \((i,j)\to (i+1,j),(i,j)\to (i+1,j+1)\),于是我们得到了一张平面图!对于初始状态,就只用考虑 \((0,x_i),i\in[1,k]\)。概率实际上是总合法方案数除以 \(2^{nk}\),算方案数就是板子 \(\text{LGV Lemma}\) 了,然而此题没有确定末状态,如果硬上行列式就需要枚举末状态,这个复杂度是不可承受的。
相比而言,LHS 反倒显得好算。设 \(dp(S,j)\) 为只考虑集合 \(S\) 内的机器人,所有机器人结尾位置 \(\leqslant j\) 的带行列式系数的和,转移如下
其中 \(\text{upper}(S,i)\) 是 \(S\) 集合中 \(>i\) 的数,因为枚举 \(i\) 表示增加结尾为 \(j\) 的机器人 \(i\),它就可以用来算逆序对增量。由于要求 末状态位置两两不同,所以直接从 \(dp(,j-1)\) 处贡献即可,不用担心算重的问题。复杂度 \(\mathcal O(nk2^k)\).
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 <iostream>
using namespace std;
const int MAXN = 1044;
const int mod = 998244353;
int inc(int x,int y) { return x+y>=mod?x+y-mod:x+y; }
int qkpow(int x,int y,int r=1) {
for(; y; y>>=1, x=1ll*x*x%mod)
if(y&1) r=1ll*r*x%mod; return r;
}
int dp[MAXN][MAXN<<1];
int k, n, p[20], C[MAXN][MAXN], m;
int c(int n,int m) {
if(m<0 || n<m) return 0;
return C[n][m];
}
int main() {
k=read(9), n=read(9); C[0][0]=1;
for(int i=1;i<=n;++i) { C[i][0]=1;
for(int j=1;j<=i;++j)
C[i][j] = inc(C[i-1][j-1],C[i-1][j]);
} int lim = 1<<k;
for(int i=1;i<=k;++i) m=max(m, n+(p[i]=read(9)+1));
for(int i=0;i<=m;++i) dp[0][i]=1;
for(int s=1; s<lim; ++s) for(int j=1;j<=m;++j) {
dp[s][j] = dp[s][j-1];
for(int i=k, up=0; i>0; --i) if(s>>(i-1)&1) {
int t = s^(1<<i-1), coe = (up&1)?mod-1:1; ++ up;
dp[s][j] = inc(dp[s][j], 1ll*coe*dp[t][j-1]%mod*c(n,j-p[i])%mod);
}
} print(1ll*dp[lim-1][m]*qkpow(mod+1>>1,n*k)%mod,'\n');
return 0;
}