总结:第一次打,有点拉,(A,B)都是简单题,就不说了
(D.)
- 题目链接:D - Hamiltonian Cycle
- 题意简述:给一张点数为(P-1)的图,每个点(x)会向(x * amod P),(x * b mod P),(x * a^{-1} mod P),(x * b^{-1} mod P)的连边,求一条起点为(1)的经过所有点的哈密顿回路,或断言不存在
- (P leq 1e5,1leq a,bleq P-1)
(solution)
- 考虑设(S_a = {x|x = a^i(mod p)}),设(n = ord_a = |S_a|),设(m = min{x|b^x in S_a}),那么注意到所有可以通过(1)到达的数都可以写成(a^x * b^y),记作二元组((x,y)),注意到(a^x = a^{x mod n}),且(b^y = b^{y mod m} * a ^ {z}(z in N^{*}))所以我们不妨令(x in [0,n),y in [0,m)),容易发现即使做了这样的限制,(1)能到达的所有的数仍然能写成(a^x*b^y)的形式
- 于是我们可以通过判断(n * m == P-1)来判断有没有解,如果有解,我们就可以把题目转化成一个(n * m)的表格,可以向上向下向左向右走(这里分别对应题目连的四种边),是否存在哈密顿回路
- 这里你可以自己画画图手玩出来,因为不好描述就直接放官方题解了D official editorial
/*D - Hamiltonian Cycle*/
#include<bits/stdc++.h>
using namespace std;
int read(){
char c = getchar();
int x = 0;
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
return x;
}
const int _ = 2e5 + 7;
int P,a,b,inva,invb;
int qpow(int x,int y){
int ans = 1;
while(y){
if(y & 1) ans = 1ll * ans * x % P;
x = 1ll * x * x % P;
y >>= 1;
}
return ans;
}
int used[_];
int main(){
freopen("D.in","r",stdin);
P = read();a = read(),b = read();
inva = qpow(a,P-2),invb = qpow(b,P-2);
int x = 1;int oa = 0;
while(1){
oa++;
x = 1ll * x * a % P;
if(x == 1) break;
}
x = 1;int ob = 0;
while(1){
ob++;
x = 1ll * x * b % P;
if(x == 1) break;
}
if(oa == P - 1){
x = 1;
puts("Yes") ;
while(1){
printf("%d ",x);
x = 1ll * x * a % P;
if(x == 1) break;
}
printf("%d ",1);
return 0;
}
if(ob == P - 1){
x = 1;
puts("Yes");
while(1){
printf("%d ",x);
x = 1ll * x * b % P;
if(x == 1) break;
}
printf("%d ",1);
return 0;
}
int n = oa;x = 1;
for(int i = 1; i <= n; ++i) x = 1ll * x * a % P,used[x] = 1;
int m = 1;x = 1;
while(1){
x = 1ll * x * b % P;
if(used[x]) break;
m++;
}
if(1ll * n * m != P - 1) return puts("No"),0;
puts("Yes");
if(m & 1) swap(n,m),swap(a,b),swap(inva,invb);
// cout<<n<<' '<<m<<endl;
printf("%d ",1);
x = 1;
memset(used,0,sizeof(used));
oa = 0,ob = 0;
for(int i = 1; i < n; ++i){
x = 1ll * x * a % P,printf("%d ",x),used[x]++;
// if(i == 3) cout<<oa<<' '<<ob<<"!!!"<<endl;
}
for(int i = 1; i < m; ++i) x = 1ll * x * b % P,printf("%d ",x),used[x]++;
for(int i = 1; i < n; ++i) x = 1ll * x * inva % P,printf("%d ",x),used[x]++;
bool lst = 0;
for(int i = 1; i < m; ++i){
x = 1ll * x * invb % P,printf("%d ",x),used[x]++;
if(i == m - 1) break;
if(lst){
for(int j = 1; j < n - 1; ++j){
x = 1ll * x * inva % P,printf("%d ",x),used[x]++;
}
}
else{
for(int j = 1; j < n - 1; ++j) x = 1ll * x * a % P,printf("%d ",x),used[x]++;
}
lst ^= 1;
}
return 0;
}
(E).
- 题目链接:E - Avoid Permutations
- 题目简述:给一个表格([0,n+1] * [0,n+1]).
- 假设存在一个长度为(n)的排列(P),((i,P_i))为障碍点,我们定义(f(P))为不经过(P)所表示的障碍点,从((0,0))出发,只能向下向右,到达((n+1,n+1))的方案数
- 现在给你一个(A_i),其中(A_i in (1,n) cup {-1}),我们定义一个排列(P_i)为好的,当且仅当(forall i,if(A_i != -1),P_i = A_i),对于所有的(P),你要求(sum_Pf(P))
- (n leq 200)
(solution)
- 我们考虑我们如果直接(dp),很容易可以得到不经过(A_i != -1)的二元组((i,A_i))的点的方案数,但是我们难以保存那些(= -1)的点中(P_i)究竟是什么(因为是排列,必须保证每个数只出现一次)
- 注意以下我们求的所有东西都是建立在不经过已经确定的障碍点的前提下,不妨设这样的点数为(K)
- 考虑子集反演即容斥,注意到我们要求的是一个障碍点都没有经过即(f(empty)),那么我们有
- (g(S) = sum_{S subseteq T}f(T))
- (f(empty) = sum_{T}g(T)*(-1)^{|T|})
- 考虑组合意义,(g(T))即钦定走了(T)这个集合的障碍点,剩下的不管,那么我们可以直接(dp)经过了(T)中的点的方案数,然后再乘以((n - K - |T|)!)即为(g(T))
- 我们可以设(dp(i,j,k,o1,o2)),表示当前到达((i,j)),已经选择的(T)满足(|T| = k)(即已经钦定了(k)个点),行列是否有已经钦定的点的方案数,转移是显然的
- 复杂度(O(n^3))
/*E - Avoid Permutations*/
#include<bits/stdc++.h>
using namespace std;
int read(){
char c = getchar();
int x = 0,f = 1;
while(c < '0' || c > '9') f = (c == '-')?-1:f,c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
return x * f;
}
const int _ = 2e2 + 7;
int dp[_][_][_][2][2];
int fac[_];int p[_],n;
bool row[_],col[_];
#define mod 998244353
void add(int &x,int y){
x += y - mod;
x += (x >> 31) & mod;
}
int main(){
memset(p,-1,sizeof(p));
fac[0] = 1;
n = read();for(int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i-1] * i % mod;
int K = 0;
for(int i = 1; i <= n; ++i){
p[i] = read(),K += (p[i] != -1);
if(p[i] != -1) row[i] = 1,col[p[i]] = 1;
}
dp[0][0][0][0][0] = 1;
for(int i = 0; i <= n + 1; ++i){
for(int j = 0; j <= n + 1; ++j){
for(int k = 0; k <= n - K; ++k){
for(int o1 = 0; o1 <= 1; ++o1){
for(int o2 = 0; o2 <= 1; ++o2){
int val = dp[i][j][k][o1][o2];
if(!val) continue;
/*向右走*/
int ni = i,nj = j + 1;
if(p[ni] != nj){
if(o1 == 0 && !row[ni] && !col[nj] && ni >= 1 && ni <= n && nj >= 1 && nj <= n){/*钦定下一个是障碍点*/
add(dp[ni][nj][k+1][1][1],val);
}
add(dp[ni][nj][k][o1][0],val);
}
/*向下走*/
ni = i + 1;nj = j;
if(p[ni] != nj){
if(o2 == 0 && !row[ni] && !col[nj] && ni >= 1 && ni <= n && nj >= 1 && nj <= n){
add(dp[ni][nj][k+1][1][1],val);
}
add(dp[ni][nj][k][0][o2],val);
}
}
}
}
}
}
int ans = 0;
for(int i = 0; i <= n - K; ++i){
int v = 1ll * dp[n+1][n+1][i][0][0] * fac[n-K-i] % mod;
if(i & 1) add(ans,mod-v);
else add(ans,v);
}
printf("%d
",ans);
return 0;
}