A. Maximal Binary Matrix(Codeforces 803A)
思路
本題的入手點是,先滿足“字典序最小”的要求,再滿足對稱性(就比較容易了)。
由於題目要求解的“字典序”最小,所以可以先按照行從上到下,再按照列從左到右的順序爲矩形填上
- 當
i=j 時,由於當前填的位置位於對角線上,填上後肯定不會影響對稱性,所以能填就填。什麼是不能填的情況呢?如果填1 的次數沒有剩餘了的話就不能填了。 - 當
i!=j 時,由於當前填的位置不位於對角線上,所以如果要在(i,j) 網格內填數的話就得在(j,i) 網格內填數。有如下情況就不能填:剩餘的填1 次數爲1 ,(i,j) 網格內已經填過1 了。
代碼
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
int n, k, a[maxn][maxn];
// 輸出矩陣
void output() {
for(int i = 1; i <= n ;i++) {
for(int j = 1; j <= n; j++) {
cout << a[i][j] << ' ';
}
cout << endl;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
// k比格子還要多的時候
if(k > n * n) {
cout << -1 << endl;
return 0;
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(k == 0) {
output();
return 0;
}
if(a[i][j] == 1) {
continue;
}
// 填對角線位置
if(i == j) {
a[i][j] = 1;
k--;
}
// 填非對角線位置
else if(k >= 2) {
a[i][j] = a[j][i] = 1;
k -= 2;
}
}
}
if(k > 0) {
cout << -1 << endl;
}
else {
output();
}
return 0;
}
B. Distances to Zero(Codeforces 803B)
思路
本題的入手點在於將問題“找離某位置最近的
我們只考慮“找在數組中的位置的
代碼
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10, INF = 3e5;
int n, last, a[maxn], b[maxn], c[maxn];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
// 從左到右掃描數組,尋找離位置i最近的左側的0
last = -1;
for(int i = 0; i < n; i++) {
if(a[i] == 0) {
b[i] = 0;
last = i;
}
else if(last >= 0) {
b[i] = i - last;
}
else {
b[i] = INF;
}
}
// 從右到左掃描數組,尋找離位置i最近的右側的0
last = -1;
for(int i = n - 1; i >= 0; i--) {
if(a[i] == 0) {
c[i] = 0;
last = i;
}
else if(last >= 0) {
c[i] = last - i;
}
else {
c[i] = INF;
}
}
// 輸出答案
for(int i = 0; i < n; i++) {
cout << min(b[i], c[i]) << ' ';
}
return 0;
}
C. Maximal GCD(Codefoeces 803C)
思路
本題的入手點爲考慮到數列的
這題要構造一個數列,其需要滿足兩個條件,一個是個數爲
其中
最後就是實現的時候,由於輸入的數據太大,需要注意溢出的問題。
代碼
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
vector <ll> divisor;
ll n, k, ub, a, s, S;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
// 按照以1首項1爲公差的數列和求出k的上界
// 以避免下面出現的懲罰溢出
ub = 2 * (ll)(sqrt(1.0 * n) + 0.5);
if(k > ub) {
cout << -1 << endl;
return 0;
}
// 預處理出n的約數
for(ll i = 1; i * i <= n; i++) {
if(n % i > 0) {
continue;
}
divisor.push_back(i);
if(i != n / i) {
divisor.push_back(n / i);
}
}
// 對n的約數d
for(ll d : divisor) {
// 避免溢出,提前判斷
if(k * (k - 1) / 2 >= n / d) {
continue;
}
// 求出數列的和
s = k * (k - 1) * d / 2;
if(n - s <= (k - 1) * d) {
continue;
}
// 更新最大公約數最小的解
if(d > a) {
a = d;
S = s;
}
a = max(a, d);
}
if(a == 0) {
cout << -1 << endl;
return 0;
}
// 輸出數列
for(ll i = 1; i < k; i++) {
cout << a + (i - 1) * a << ' ';
}
cout << n - S << endl;
return 0;
}
D. Magazine Ad(Codeforces 803D)
思路
本題的入手點是,利用數據的“二分性”,將求最值的問題轉化爲驗證某個值是否可行。
我們可以枚舉最長的行的長度
但是由於
代碼
#include <bits/stdc++.h>
using namespace std;
string s;
vector <int> vec;
int k, last, l, r;
// 判斷對給定的最長行的長度是否有解
bool ok(int x) {
int cnt = 0, sum = 0;
// 對於每個“單詞”
for(int i = 0; i < vec.size(); i++) {
if(vec[i] > x) {
return false;
}
// 把該單詞算在下行
if(sum + vec[i] > x) {
sum = vec[i];
cnt++;
}
// 把該單詞算在該行
else {
sum += vec[i];
}
}
if(sum > 0) {
cnt++;
}
return cnt <= k;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> k;
getline(cin, s);
getline(cin, s);
last = 0;
// 預處理處單詞
for(int i = 1; i < s.size(); i++) {
if(s[i] == ' ' || s[i] == '-') {
vec.push_back(i - last + 1);
last = i + 1;
}
}
// 有可能還有最後一個單詞
if(last <= s.size() - 1) {
vec.push_back(s.size() - 1 - last + 1);
}
// 二分查找
l = -1;
r = s.size();
while(r - l > 1) {
int mid = (l + r) / 2;
if(ok(mid) == true) {
r = mid;
}
else {
l = mid;
}
}
cout << r << endl;
return 0;
}
E. Roma and Poker(Codeforces 803E)
思路
本題的入手點是,由“多階段決策”這個特徵想到用
在不考慮數據範圍的情況下顯然可以用搜索來解決,但是本題的數據範圍顯然不允許我們這樣做。因此需要另闢蹊徑。不妨令狀態
s[i] | 可能發生的情況 |
---|---|
L | 此時只能是輸掉第i局 |
W | 此時只能是贏下第i局 |
D | 此時可以打平第i局 |
? | 此時可以輸,可以贏,也可以打平第i局 |
雖然每次有這麼多種狀態轉移情況,但是在每個狀態
s[i] | 狀態轉移方程 |
---|---|
L | d[i][j] |= d[i-1][j + 1] |
W | d[i][j] |= d[i - 1][j - 1] |
D | d[i][j] |= d[i - 1][j] |
? | (此處應寫上以上的三條方程) |
最後如果狀態
j 有可能是負數。解決方案是將所有的j都加上一個偏移量,將原來j 可能落在的區間[−k,k] 映射到[lb,ub] 上去(lb≥0,ub≥0 )。- 由於在比賽的中途比賽不能終止(也就是一定要恰好在第
n 局結束),所以對於任意i<n ,在狀態轉移中,任意狀態不能轉移到狀態(i,lb) 或(i,ub) 。
轉移結束後還沒有完成最終任務,我們還需要輸出一個解。爲了構造出一個解,我們可以用矩陣
代碼
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2010, mid = 1000;
stack <char> ans;
string s;
int n, k, lb, ub, d[maxn][maxn], p[maxn][maxn];
// 輸的情況的狀態轉移
void lose(int i, int j) {
if(j == ub || !d[i - 1][j + 1]) {
return;
}
d[i][j] = 1;
p[i][j] = j + 1;
}
// 贏的情況的狀態轉移
void win(int i, int j) {
if(j == lb || !d[i - 1][j - 1]) {
return;
}
d[i][j] = 1;
p[i][j] = j - 1;
}
// 平局的情況的狀態轉移
void draw(int i, int j) {
if(d[i - 1][j] == 0) {
return;
}
d[i][j] = 1;
p[i][j] = j;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k >> s;
ub = mid + k;
lb = mid - k;
s = " " + s;
d[0][mid] = 1;
for(int i = 1; i <= n; i++) {
for(int j = lb; j <= ub; j++) {
// 比賽不能提前結束
if(i < n && (j == ub || j == lb)) {
continue;
}
// 根據s[i]的值來決定狀態的轉移方式
if(s[i] == 'L') {
lose(i, j);
}
else if(s[i] == 'D') {
draw(i, j);
}
else if(s[i] == 'W') {
win(i, j);
}
else {
lose(i, j);
draw(i, j);
win(i, j);
}
}
}
// 無解的情況
if(!d[n][lb] && !d[n][ub]) {
cout << "NO" << endl;
return 0;
}
// 回溯構造解
int i = n;
int j = d[n][lb] ? lb : ub;
while(i > 0) {
if(p[i][j] > j) {
ans.push('L');
}
else if(p[i][j] == j) {
ans.push('D');
}
else {
ans.push('W');
}
j = p[i--][j];
}
// 逆序輸出結果(這裏用了一個棧)
while(!ans.empty()) {
cout << ans.top();
ans.pop();
}
return 0;
}
F. Coprime Subsequences(Codeforces 803F)
思路
本題的入手點是,運用分類的思想,正難則反的思想和容斥的思想來解決計數問題。
在開始描述解法之前先做一些鋪墊。令
回到問題中。我們要求的是
a序列的滿足性質“所有的數兩兩互質”的子序列的個數
“兩兩互質”聽上去不那麼清晰,可以將上述描述改成
a序列中滿足性質“所有的數的最大公約數gcd爲1”的子序列的個數
就算描述變成這樣也還是不容易有思路,那麼根據“正難則反”的思想,我們可以把上述描述進一步變形:
a序列中不滿足性質“所有數的gcd爲2或3或…或max{a_i, i <= n}”的子序列的個數
注意是“不滿足”那個性質的子序列的個數。那麼上述表述所描述的值就可以用容斥原理轉化成求
其中
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5, mod = 1e9 + 7;
bool vis[maxn];
int n, a, f[maxn], mu[maxn], prime[maxn];
ll ans, cnt[maxn];
// 篩法預處理出莫比烏斯函數
// 存在mu[]中
void init(int N) {
memset(vis, 0, sizeof(vis));
mu[1] = 1;
int cnt = 0;
for(int i = 2; i < N; i++) {
if(!vis[i]) {
prime[cnt++] = i;
mu[i] = -1;
}
for(int j = 0; j < cnt && i * prime[j] < N; j++) {
vis[i * prime[j]] = 1;
if(i % prime[j]) {
mu[i * prime[j]] = -mu[i];
}
else {
mu[i * prime[j]] = 0;
break;
}
}
}
}
// 枚舉統計a數組中的各個數的約數的出現次數
void divisor(int n) {
for(int i = 1; i * i <= n; i++) {
if(n % i == 0) {
cnt[i]++;
if(i != n / i) {
cnt[n / i]++;
}
}
}
}
// 計算快速冪的函數
ll modPow(ll a, ll n, ll mm) {
ll ans = 1;
for(; n > 0; n >>= 1) {
if(n & 1) {
ans = (ans * a) % mm;
}
a = (a * a) % mm;
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
init(maxn);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a;
divisor(a);
}
// 計算上面提到的num()函數
for(int i = 1; i < maxn; i++) {
f[i] = modPow(2, cnt[i], mod) - 1;
f[i] = (f[i] + mod) % mod;
}
// 容斥原理
for(int i = 1; i < maxn; i++) {
ans = (ans + mu[i] * f[i] + mod) % mod;
}
cout << ans << endl;
return 0;
}
其它題目略