(偷偷吐槽网上有些题解没说清楚为什么if(n>s) swap(n,s)
题意
给定T组数据,以及n、s ((1 leq T leq 100) (1 leq n leq 1e9) (0 leq s leq 1e9))
问是否能得到一个s+1 -> s+n共n个数的排列,使得每个位置i上的值(v_i)被i整除
突破口在2e9内两个相邻素数间隔不会太大,所以大区间特判,小区间暴力二分图匹配
然而还要考虑特殊情况,比如当s=0,s=1显然成立,与n的值无关。
那么如果s=2呢?同样显然成立.理由是
if n<=2, 那么将3、4填入位置1、2中,
if n > 2, 那么[1,n]与[s + 1, s + n]将会有重叠,重叠部分直接匹配,剩下两个相邻的数必然可以放入1、2中
继续考虑s=3,你会发现很容易找到满足题意的情况,但也存在的反例,如当(n+s)%6==1的一些情况
虽然没有太过直接的规律,但在过程中你会发现如果区间有重叠,那么相同的数直接匹配后,再在剩余的数中寻找匹配是与我们能找到满足题意的序列互为充要条件
(因为如果a能放在b位置,b能放在c位置,必然a能放在c位置上,类似这样去思考
那么if(n>s) swap(n, s)与原题就是等价的了
而且这样还能解决我们前面提到的s较小,n很大也可能存在解的问题
总体来说,就是对于出现区间重叠的情况,我们通过交换n,s来缩小区间(事实上只有这块区间需要我们寻找匹配,这样才能利用区间间隔特判),然后大区间特判,小区间暴力
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int maxn = 45000;
int T;
int n, s;
bool is_prime[maxn];
int prime[maxn];
int match[1500];
bool used[1500];
vector<int> G[1500];
int V;
void add_edge(int u, int v) {
G[u].push_back(v); G[v].push_back(u);
}
bool dfs(int v) {
used[v] = true;
for (int i = 0; i < G[v].size(); i++) {
int u = G[v][i];
int w = match[u];
if (w < 0 || (!used[w] && dfs(w))) {
match[u] = v;
match[v] = u;
return true;
}
}
return false;
}
int bi_match() {
int res = 0;
memset(match, -1, sizeof(match));
for (int v = 0; v < V; v++) {
if (match[v] < 0) {
memset(used, 0, sizeof(used));
if (dfs(v)) ++res;
}
}
return res;
}
int main() {
scanf("%d", &T);
for (int Te = 1; Te <= T; Te ++) {
printf("Case #%d: ", Te);
scanf("%d%d", &n, &s);
if (s <= 2) {
puts("Yes");
} else {
if (s < n) swap(n, s);
if (n <= 500) {
V = (n << 1);
for (int i = 0; i < V; i++) G[i].clear();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if ((s + i) % j == 0) {
add_edge(n + i - 1, j - 1);
}
}
}
int ans = bi_match();
if (ans == n) {
puts("Yes");
} else {
puts("No");
}
} else {
puts("No");
}
}
}
return 0;
}