NOIP2024 遗失的赋值

NOIP2024 遗失的赋值

给定一堆一元限制,问有多少种二元限制的方案,使得至少存在一种对 \(x_{1 \sim n}\) 赋值方法满足所有限制。

观察数据发现如下性质:

1、一元限制的作用:对于 \(x_{c_j} = d_j\),当 \(a_{c_j} = d_j\) 是,强制 \(x_{c_j + 1} = b_{c_{j}}\)

2、当 \(m = 1\) 时,答案是 \(v^{2n - 2}\)。因为模拟后可以发现这个限制没有什么用。

3、当没有一元限制干扰时,尽量按 \(x_i \ne a_i\) 填,限制最宽松(即需要满足的二元限制最少)。

4、最后一个一元限制不做限制。两个一元限制隔得较远,相当于在传递限制。

现在我们来考虑 \(m = 2\) 的情况。

一开始我以为这个贡献与一元限制的距离无关,就直接用相邻的计算方法算了所有,即 \([(v - 1) \times v + 1] ^ {len}\)\(len\) 就是两个一元限制的距离。结果后来发现,对答案分解质因数后,里面死活会有一些根本不会在我的式子里出现的系数,这说明要么是不受限制的贡献统计有问题,要么是受限制的贡献统计有问题。中途犹豫了很久以为是不受限制的部分出错了,最后终于拿下性子手搓了一个距离为 \(2\) 的样例,发现贡献长得非常丑陋:

\[(v - 1)v \times v^2 + v \times ((v -1)v \times v + v \times ((v - 1)v + 1) ) \]

解释一下,其实容易发现这个式子有明显的递归特性,当下一个点不是另一个一元限制时,当前点裂成两种情况:

情况一:使 \(a_i\) 摆脱上一个一元限制,则 \(a_i\)\(v - 1\) 种填法,\(b_i\) 就可以随便填了,故贡献系数使 \((v - 1)v\),那么下一个位置 \(a, b\) 都可以随便填,贡献是 \(v^2\)

情况二:使 \(a_i\) 保持限制,则 \(a_i\)\(1\) 种填法,\(b_i\)\(v\) 种填法,然后情况继续分裂下去。直到最后一个保持限制的位置,只有 \(1\) 的贡献。(就是上述式子最后那个 \(1\))。

那么距离更大的情况,就继续递归就可以了。

经过一番艰苦的化简,发现:

距离为 \(1\)\(v^4 - v^2 + v^1\)

距离为 \(2\)\(v^6 - v^3 + v^2\)

\(\vdots\)

距离为 \(i\)\(v^{2(i + 1)} - v^{i + 1} + v^i\)

于是问题就变得简单了,只需要分别处理每两个一元限制的贡献,最后乘起来即可。

时间复杂度 \(O(Tm \log n)\)

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l); i <= (r); ++ i)
#define G(i,r,l) for(int i(r); i >= (l); -- i)
#define int ll
using namespace std;
using ll = long long;
const int mod = 1e9 + 7;
const int N = 2e5;
int cnt = 0; 
int quickmod(int x, int y){
	int ret = 1;
	while(y){
		if(y & 1) ret = ret * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ret;
}
int n, m, v, T, tt;
int a[N];
map<int, int>mp;
namespace task{
	void Main(){
		assert(mp[0] == 0);
		cin >> n >> m >> v;
		int flag = 1;
		int tot = 0;
		int ret = 1;
		F(i, 1, m){
			int c, d;
			cin >> c >> d;
			if(mp[c] > 0){
				if(mp[c] != d) flag = 0;
			}
			else {
				mp[c] = d; 
				++ tot; 
			}
			a[i] = c;
		}
		if(!flag){
			cout << "0\n";
		}
		else{
			int r = m;
			sort(a + 1, a + r + 1);
			r = unique(a + 1, a + r + 1) - a - 1;
			F(i, 2, r){
				int len = a[i] - a[i - 1] - 1;
				int tmp = (quickmod(v, 2 * len + 2) - quickmod(v, len + 1) + quickmod(v, len)) % mod;
				ret = ret * tmp % mod;
			}
			ret = ret * quickmod(v * v % mod, a[1] - 1) % mod;
			ret = ret * quickmod(v * v % mod, n - a[r]) % mod;
			cout << (ret + mod) % mod << '\n';
		}
		mp.clear();
	}
} 
signed main(){
//	freopen("assign.in", "r", stdin);
//	freopen("assign.out", "w", stdout);
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> T; tt = T;
	while(T --) task::Main();
	return fflush(0), 0;
}
posted @ 2025-08-31 15:32  superl61  阅读(2)  评论(0)    收藏  举报