2020-2021 Winter Petrozavodsk Camp, UPC contest 部分题解 ABI
A. Adjacent Rooks
题意
给定一个n*n大小的棋盘,放入n个棋子使得每个棋子占一行一列(任意2个棋子不在同一行且不在同一列),询问恰好有k对棋子斜对角相邻的摆放方案数
思路
可以看出来,可以用行号来表示每一列棋子的位置,生成一个全排列,每次交换时候都是全排列元素的置换
那么,考虑一个dp转移,由 i 到 i+1 ,有把 i+1 这个数放到这个全排列的任意一个插空位置(含左右两边),也就是有i+1种插入方式
下面给出一种4转移到5的方案表示,(.表示可插入位置,X表示放置的棋子,O表示空位)
O X O O O X O O O X O O O
X O O O X O O O X O O O O
O O O X ---> O O O X ---> O O O O X
O O X O O O X O O O O X O
. . X . . O O X O O
接下来,我们考虑新增的棋子对原棋盘的贡献
如果前两个棋子有贡献
- 如果顺着前最后两个棋子的方向(如上图),贡献 +1
- 如果逆着最后两个棋子的方向(如上图将第3、4列置换),贡献 +1-1 = +0
- 如果插入到其他地方,考虑是否有破坏贡献,如果有,贡献 -1;如果无,贡献 +0
如果前一个棋子孤立/无贡献
- 同上考虑4种情况的贡献(具体可以看代码,写的很清楚)
那么我们可以定义一个函数
其中,\(i\) 表示当前有几个棋子,\(j\) 表示当前带来的贡献恰好为 \(j\),\(k\)表示上两个棋子是正对角线排列or反对角线排列or上一个点孤立
状态转移大致为:
代码
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<(b);++i)
#define per(i,a,b) for(int i=(b-1);i>=a;--i)
using namespace std;
using i64 = long long;
using ll = long long;
using vi = vector<int>;
#define all(x) x.begin(),x.end()
#define sz(x) x.size()
#define pb push_back
const int N = 1006,K = 1006;
const i64 MOD = 1e9+7;
i64 f[N][K][3] = {0};
// 0: OOO 1: OXO 2: OXO
// OXO OOX XOO
void add(i64&o1,i64 o2) {
o1 += o2;
if(o1>=MOD) o1-=MOD;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
f[1][0][0] = 1;
for(int i=1;i<=1000;++i) {
for(int j=0;j<i;++j) {
add(f[i+1][j+1][1], f[i][j][0]);
add(f[i+1][j+1][2], f[i][j][0]);
if(j) add(f[i+1][j-1][0], j*f[i][j][0]%MOD);
add(f[i+1][j][0], (i+1-2-j)*f[i][j][0]%MOD);
add(f[i+1][j+1][1], f[i][j][1]);
add(f[i+1][j][2], f[i][j][1]);
if(j) add(f[i+1][j-1][0], (j-1)*f[i][j][1]%MOD);
add(f[i+1][j][0], (i+1-1-j)*f[i][j][1]%MOD);
add(f[i+1][j+1][2], f[i][j][2]);
add(f[i+1][j][1], f[i][j][2]);
if(j) add(f[i+1][j-1][0], (j-1)*f[i][j][2]%MOD);
add(f[i+1][j][0], (i+1-1-j)*f[i][j][2]%MOD);
}
}
int t;
cin >> t;
while(t--) {
int n,k;
cin >> n >> k;
cout << (f[n][k][0] + f[n][k][1] + f[n][k][2]) % MOD << '\n';
}
return 0;
}
B. Beautiful Permutation (非正解)
据说是最难的题,但是可以乱搞
而且题解上写的完全看不懂
题意
给你一个n,构造一个n长度大小的 0到n-1 的全排列a,得到
使得数组b亦为全排列
思路
乱想一下,对a、b求个和
显然,要使得两式相等,负的部分得为
因为\(n\)是整数,所以显然 \(n\ mod \ 4 == 0 \ or\ 1\) 时才能整除,其他情况无解
那么考虑构造样例
0 1 2 3 // i
3 0 2 1 // a_i
3 1 0 2 // b_i
没什么思路,构造个长一点的
0 1 2 3 4 5 6 7 8
8 7 6 5 4 4 3 2 1 // 多了个4
8 6 4 2 0 1 3 5 7 // b_i
除了中间那个4,基本构造完成了,看样子乱搞一下中间部分应该行
然后考虑到要满足\(X\)那个条件,我们尽量让它好看一点,为O部分和\的部分的一半就是我们要减去的东西
* *
* * * *
* * * * * *
* * * * * * * *
* * * * * O \ * * *
* * * * * * O O O \ * *
* * * * * * * O O O O O \ *
* * * * * * * * O O O O O O O \
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
先把中间干烂
0 1 2 3 4 5 6 7 8
8 7 6 0 3 2 1
8 6 4 4 3 5 7 // b_i
好像下面又多了个4。。。怎么办,再退后一步,先搞其他的
0 1 2 3 4 5 6 7 8
8 7 0 2 1
8 6 4 5 7 // b_i
0 1 2 3 4 5 6 7 8
8 7 5 0 2 1
8 6 3 4 5 7 // b_i
*
* *
* * *
* * * *
O * * * *
O O O * * *
- O O O - O *
- - O O O - - O
0 1 2 3 4 5 6 7 8
6还是没地方放,然而我们还有3+4+3+2没减,考虑枚举一下位置吧,放3下面肯定不合适,5下面或者6下面
对了,刚好缺个0,要不丢6下面?还解决了4+2,接下来减掉2个3就好,刚好3没用过
0 1 2 3 4 5 6 7 8
8 7 5 0 6 2 1
8 6 3 4 0 5 7 // b_i
0 1 2 3 4 5 6 7 8 // i
8 7 5 4 0 3 6 2 1 // a_i
8 6 3 1 4 2 0 5 7 // b_i
完美!
回忆一下怎么构造的:
- 先是左一次右一次,凑 8765
- 然后4用 4-0 来凑
- 接下来凑0,这里使用 6-6
- 然后再用同样的办法凑 321
- 注:中间左边的数,全都是a[i]>i,右边除了6全是i>a[i]
最终达到下图中间这座小山的左右两边分别用最小的几个数凑出,然后中间部分那几个和斜线部分除以2用一个数来凑
*
* *
* * *
* * * *
| \ * * *
O | O \ * *
O O | O O \ *
O O O | O O O \
0 1 2 3 4 5 6 7 8
检验一下12和13,符合预期,上机!(虽然训练时候没想出来x)
代码
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<b;++i)
using namespace std;
using i64 = long long;
const int N = 1e6+6;
int a[N];
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int n;
cin >> n;
if(n%4==2 || n%4==3) {
cout << "NO";
return 0;
}
cout << "YES\n";
a[n-1-n/4] = n-1-n/4;
for(int i=0;i<n>>2;++i) {
a[i] = n-i-1;
a[n-1-i] = i+1;
}
for(int i=0;n/4+i<n-1-n/4-i-1;++i) {
a[n/4+i] = n-1-n/4-i-1;
a[n-1-n/4-i-1] = n/4+i+1;
}
a[(n-1)/2] = 0;
for(int i=0;i<n;++i) {
cout << a[i] << ' ';
}
return 0;
}
I. Interesting Scoring Systems
细节题,想不清楚就容易WA,建议和队友多讨论讨论
题意
给了3种比赛系统,2种为计分系统,A:win+2,lose+0,draw+1,B:win+3,lose+0,draw+1,然后第三种是,类似大逃杀模式,只要1有打败2,就可以视作1打败了2打败的队伍(连出有向边),最后决出胜者(有且仅有1位)。现在给你他们在AB系统下的分数,问C系统是否可能让第一个人取胜。
思路
分类:
- n==1,yes
- n==2,第一个人不能没赢过,第二个人不能赢
- n==3,第一个人不能没赢过,总的连出有向边个数不能少于n-1
做完了。。。嗯…就这样……
至于如果不知道为什么,不是题没读懂就是细节没考虑清楚,举例就知道了
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
using ll = long long;
using pii = pair<int,int>;
const int N = 1e6+6;
int a[N],b[N];
void sol() {
int n;
cin >> n;
for(int i=0;i<n;++i) cin >> a[i];
for(int i=0;i<n;++i) cin >> b[i], a[i]=b[i]-a[i];
if(n==1) {
cout << "YES\n";
return;
}
if(n==2) {
if(a[0]>=1 && a[1]==0) {
cout << "YES\n";
}else {
cout << "NO\n";
}
return;
}
int tot = 0;
for(int i=0;i<n;++i) {
tot += a[i];
}
if(a[0]>=1 && tot>=n-1) {
cout << "YES\n";
}else {
cout << "NO\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--) sol();
return 0;
}