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\) 的带行列式系数的和,转移如下

\[dp(S,j)=dp(S,j-1)+\sum_{i\in S}(-1)^{\text{upper}(S,i)}dp(S\setminus \{i\},j-1)\binom{n}{j-x_i} \]

其中 \(\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;
}
posted on 2021-08-30 21:43  Oxide  阅读(68)  评论(0编辑  收藏  举报