- 寫在前面
- L
- K
- B
- D
- E
- J
- I
- G
- C
- 寫在最後
寫在前面
補題地址:https://codeforces.com/gym/105336
以下按個人向難度排序。
唉唉唐吧真是
我去居然還能打出名額哈哈真牛逼
L
簽到。
K
簽到。
dztlb 大神直接秒了。
顯然奇數先手取 1 必勝,則偶數時為了讓自己不輸,先手和後手均會保證在取到 2 之前,取的數和沙堆的大小一直都是偶數,否則會讓對方必勝。
於是一個顯然的想法是考慮 \(\operatorname{lowbit}(n)\),發現當 \(\operatorname{lowbit}(n)\le k\) 時,僅需先手取 \(\operatorname{lowbit}(n)\) 然後模仿後手操作即可,否則一次操作後 \(\operatorname{lowbit}(n)\le k\),則後手必勝。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
int T,n,k;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin>>T;
while(T--){
bool fl=0;
cin>>n>>k;
int tt=1;
while(tt<=k){
if(n%tt==0){
if((n/tt)%2==1) fl=1;
}
tt*=2;
}
if(fl) puts("Alice");
else puts("Bob");
}
return 0;
}
B
列舉,結論。
一個顯然的發現是有序(升序或降序)數列代價最小,可以考慮調整法證明,當某兩位置不有序時,一定會對包含這兩個位置的一些區間造成更多的代價。於是僅需考慮重複元素之間的順序的貢獻即可。
注意特判全相等時升序降序是等價的。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ull unsigned long long
const int kN = 1e6 + 10;
const LL p = 998244353;
int n, a[kN], cnt[kN], num;
LL fac[kN];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
fac[1] = 1;
for (int i = 2; i <= n; ++ i) fac[i] = fac[i - 1] * i % p;
std::sort(a + 1, a + n + 1);
LL ans1 = 0;
for (int l = 1; l <= n; ++ l) {
for (int r = l; r <= n; ++ r) {
ans1 += 1ll * (a[r] - a[l]);
}
}
for (int i = 1; i <= n; ++ i) {
++ cnt[a[i]];
if (cnt[a[i]] == 1) ++ num;
}
LL ans2 = (num == 1 ? 1 : 2);
for (int i = 1; i <= n; ++ i) {
if (cnt[a[i]] == 0) continue;
ans2 = ans2 * fac[cnt[a[i]]] % p;
cnt[a[i]] = 0;
}
std::cout << ans1 << " " << ans2 % p;
return 0;
}
D
DP。
顯然的 DP,發現構造字串的方案實際構成一個完全二叉樹的結構,於是僅需考慮在構造這棵樹的過程中,統計完全位於兩棵子樹中的字串數量,以及跨越根節點的字串數量即可。
於是可以設一個顯然的狀態 \(f_{i, l, r}\) 表示對於字首 \(1~i\) 中,子串 \(t[l:r]\) 的出現次數,轉移時討論跨越子樹的字串中,根節點是否有貢獻即可。詳見程式碼。
總時間複雜度 \(O(n^4)\) 級別。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int kN = 110;
const ll p = 998244353;
ll f[kN][kN][kN], pw2[kN];
int n, m;
string s, t;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
std::cin >> s >> t;
n = s.length(), m = t.length();
s = "$" + s, t = "$" + t;
pw2[0] = 1;
for (int i = 1; i <= 10; ++ i) pw2[i] = pw2[i - 1] * 2;
for (int i = 1; i <= n; ++ i) {
for (int l = 1; l <= m + 1; ++ l) {
for (int r = 0; r < l; ++ r) {
f[i - 1][l][r] = 1;
}
}
for (int l = 1; l <= m; ++ l) {
for (int r = l; r <= m; ++ r) {
int len = r - l + 1;
if (i <= 8 && pw2[i] < len) break;
f[i][l][r] = 2ll * f[i - 1][l][r] % p;
for (int j = l; j < r; ++ j) {
f[i][l][r] += 1ll * f[i - 1][l][j] * f[i - 1][j + 1][r] % p;
f[i][l][r] %= p;
}
for (int j = l; j <= r; ++ j) {
if (s[i] != t[j]) continue;
f[i][l][r] += 1ll * f[i - 1][l][j - 1] * f[i - 1][j + 1][r] % p;
f[i][l][r] %= p;
}
}
}
}
cout << f[n][1][m];
return 0;
}
/*
aaaaaa a
abc abca
*/
E
期望。
呃呃這題場上三個人看了 4h 中間過了三道題沒做出來真唐吧
最大數量即儘量使所有字串沒有公共字首,即儘可能使每一層的節點均填滿。則答案即為:
考慮期望。插入的每個字串長度均為 \(m\),則每個字串都會佔據 \(1\sim m\) 層的一個節點。即對於建出來的字典樹,第 \(i\) 層上的所有節點,一定是對應了這 \(n\) 個字串的長度為 \(i\) 的字首。又字串隨機構造,則相當於在第 \(i\) 層上隨機選擇了 \(n\) 個節點,並求隨機選擇節點的數量。
考慮每一個節點對這一層的貢獻再乘上本層總數即為本層貢獻。考慮反面,求該節點沒有被選擇的機率(相當於限制僅能從 \(26^i - 1\) 個節點中獨立地隨便選 \(n\) 個),則可知期望的總數為:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ull unsigned long long
const int kN = 1e6 + 10;
const LL p = 998244353;
LL n, m;
LL qpow(LL x_, LL y_) {
LL ret = 1;
while (y_) {
if (y_ & 1ll) ret = ret * x_ % p;
x_ = x_ * x_ % p, y_ >>= 1ll;
}
return ret;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
std::cin >> n >> m;
LL ans = 0;
for (int i = 0; i <= m; ++ i) {
if (i <= 3 && qpow(26, i) <= n) ans += qpow(26, i);
else ans += n;
}
cout << ans % p << " ";
LL sum = 0, inv = qpow(26, p - 2), invpw = 1, pw = 1;
for (int i = 0; i <= m; ++ i) {
LL s = (1 - qpow(1 - invpw + p, n) + p) % p * pw % p;
invpw = invpw * inv % p, pw = pw * 26 % p;
sum += s, sum %= p;
}
cout << sum;
return 0;
}
J
異或,線性基
感覺線性基裸題啊呃呃
考慮令 \(c_i = a_i\oplus b_i\),並預處理 \(s_a = \oplus_{1\le i\le n} a_i, s_b = \oplus_{1\le i\le n} b_i\)。由異或的性質,則交換 \(a_i, b_i\) 對 \(s_a, s_b\) 的影響等價於令兩者均異或上 \(c_i\)。問題變為可以任意異或 \(c_i\),求 \(\min \max(s_a, s_b)\)。
考慮從高位到低按位貪心,設當前列舉到第 \(i\) 位,且有 \(\max(s_a, s_b) = s_{1}\),另一方為 \(s_{2}\):
- 若 \(s_1, s_2\) 該位均為 0,或僅有 \(s_2\) 該位為 0,則異或後不會使答案更優,跳過;
- 若 \(s_1\) 該位為 1,則考慮儘量將這一位消除,且不影響已確定的更高的位的答案。
由上述不影響更高位的要求,想到考慮對 \(c_i\) 維護線性基,然後直接在上述貪心過程中使用對應的向量消去答案中某位的 1 即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ull unsigned long long
const int kN = 1e6 + 10;
int n, a[kN], b[kN], suma, sumb, c[kN];
int ans, p[70];
void insert(int c_) {
for (int i = 60; ~i; -- i) {
if (!(c_ >> i)) continue;
if (!p[i]) {
p[i] = c_;
break;
}
c_ ^= p[i];
}
}
void solve(int sa_, int sb_) {
int x = sa_, y = sb_;
for (int i = 60; ~i; -- i) {
if (x < y) std::swap(x, y);
if (!(x >> i & 1) && !(y >> i & 1)) continue;
if ((x >> i & 1) && (y >> i & 1)) {
x ^= p[i], y ^= p[i];
} else if ((x >> i & 1) && x >= y) {
x ^= p[i], y ^= p[i];
} else {
continue;
}
}
ans = min(ans, max(x, y));
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
suma = sumb = 0;
for (int i = 1; i <= n; ++ i) std::cin >> a[i], suma ^= a[i];
for (int i = 1; i <= n; ++ i) std::cin >> b[i], sumb ^= b[i];
for (int i = 0; i <= 60; ++ i) p[i] = 0;
for (int i = 1; i <= n; ++ i) c[i] = a[i] ^ b[i], insert(c[i]);
ans = max(suma, sumb);
solve(suma, sumb);
cout << ans << "\n";
}
return 0;
}
/*
1
2
2147483646 2147483647
1 2147483647
*/
I
DP。
dztlb 大神寫的,看起來是簡單題,就不補了。
code by dztlb:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define ull unsigned long long
//
//int read()
//{
// int x = 0; bool f = false; char c = getchar();
// while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
// while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
// return f ? -x : x;
//}
const int N=505;
const int mod=998244353;
int n,m;
int a[N],b[N];
int sum[N];
int f[N][N];
int dp[N];
void solve(int k){
memset(f,0,sizeof(f));
sum[0]=0;
for(int i=0;i<=m;++i) f[i][0]=1;
for(int i=1;i<=m;++i){
for(int j=1;j<=min(i, n);++j){
if(j>sum[max(b[i]-k,0ll)]) break;
f[i][j]=f[i-1][j]+f[i-1][j-1]*max(sum[max(b[i]-k,0ll)]-j+1,0ll)%mod;
f[i][j]%=mod;
}
}
for(int i=1;i<=min(n, m);++i){
dp[k]+=f[m][i];
dp[k]%=mod;
}
}
signed main() {
// ios::sync_with_stdio(false);
// cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i) {
cin>>a[i];
sum[a[i]]++;
}
for(int i=1;i<=m;++i) cin>>b[i];
sort(b+1,b+1+m);
sort(a+1,a+1+n);
int maxx=0;
int minn=1000;
for(int i=1;i<=m;++i){
for(int j=b[i]-1;j>=1;--j){
if(sum[j]) minn=min(minn,b[i]-j),maxx=max(maxx,b[i]-j);
}
}
if(minn==1000||maxx==0){
puts("0");
return 0;
}
for(int i=1;i<=500;++i) sum[i]+=sum[i-1];
// minn=1;
// cout<<minn<<' '<<maxx<<endl;
for(int k=minn;k<=maxx;++k){
solve(k);
}
int ans=dp[maxx]*maxx%mod;
for(int i=minn;i<maxx;i++){
ans+=(dp[i]-dp[i+1]+mod)%mod*(i)%mod;
ans%=mod;
}
cout<<ans<<endl;
return 0;
}
/*
5 5
1 2 3 4 5
2 3 4 5 6
*/
G
網路流
顯然應當讓第一個人儘量在他要吃的所有菜都買單,於是可以直接計算出第一個人花的錢的上界,其他人花的錢的上界一定是第一個人的上界減 1,問能否在所有上界滿足情況下能買所有菜。
僅有上界則是顯然的最大流問題,建圖求最大流檢查是否等於所有菜的總價即可。
注意建邊時的實際意義一定要符合真實情況!
//知識點:
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const int kM = 2e6 + 10;
const LL kInf = 1e18 + 2077;
//=============================================================
int n, m, S, T, a[kN], car[kN];
int x[kN], y[kN], dish[kN];
int nodenum, maxnodenum, edgenum = 1, v[kM], ne[kM], head[kN];
int cur[kN], dep[kN];
LL w[kM];
LL flag, maxa, maxflow;
//=============================================================
void addedge(int u_, int v_, LL w_) {
v[++ edgenum] = v_;
w[edgenum] = w_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
v[++ edgenum] = u_;
w[edgenum] = 0;
ne[edgenum] = head[v_];
head[v_] = edgenum;
}
void init() {
std::cin >> n >> m;
edgenum = 1;
nodenum = n + m;
maxnodenum = n + m + 2;
S = ++ nodenum, T = ++ nodenum;
for (int i = 1; i <= maxnodenum; ++ i) head[i] = 0;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i] >> car[i];
}
maxa += car[1];
for (int i = 1; i <= m; ++ i) {
std::cin >> x[i] >> y[i] >> dish[i];
if (x[i] == 1 || y[i] == 1) maxa = std::min(1ll * a[1], maxa + dish[i]);
}
addedge(S, 1, maxa - car[1]);
for (int i = 2; i <= n; ++ i) {
if (car[i] >= maxa) flag = 1;
LL w_ = std::max(0ll, maxa - car[i] - 1);
w_ = std::min(std::max(0ll, 1ll * a[i] - car[i]), w_);
addedge(S, i, w_);
}
for (int i = 1; i <= m; ++ i) {
addedge(x[i], i + n, kInf);
addedge(y[i], i + n, kInf);
addedge(i + n, T, dish[i]);
maxflow += dish[i];
}
}
bool bfs() {
std::queue <int> q;
memset(dep, 0, (nodenum + 1) * sizeof (int));
dep[S] = 1; //注意初始化
q.push(S);
while (!q.empty()) {
int u_ = q.front(); q.pop();
for (int i = head[u_]; i > 1; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (w_ > 0 && !dep[v_]) {
dep[v_] = dep[u_] + 1;
q.push(v_);
}
}
}
return dep[T];
}
LL dfs1(int u_, LL into_) {
if (u_ == T) return into_;
int ret = 0;
for (int i = cur[u_]; i > 1 && into_; i = ne[i]) {
int v_ = v[i];
LL w_ = w[i];
if (w_ && dep[v_] == dep[u_] + 1) {
LL dist = dfs1(v_, std::min(into_, w_));
if (!dist) dep[v_] = kN;
into_ -= dist;
ret += dist;
w[i] -= dist, w[i ^ 1] += dist;
if (!into_) return ret;
}
}
if (!ret) dep[u_] = 0;
return ret;
}
LL dinic() {
LL ret = 0;
while (bfs()) {
memcpy(cur, head, (nodenum + 1) * sizeof (int));
ret += dfs1(S, kInf);
}
return ret;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
init();
if (flag) {
std::cout << "NO\n";
return 0;
}
std::cout << ((dinic() == maxflow) ? "YES" : "NO") << "\n";
return 0;
}
/*
4 3
1000 900
1000 900
1000 900
1000 900
1 2 100
1 3 100
1 4 100
3 1
100 50
70 50
100 48
3 3 1
*/
C
貪心,DP。
場上想出來那個樹形 DP 但是覺得討論起來會很麻煩就沒寫。
寫在最後
學到了什麼:
- E:每個元素均等機率情況,考慮單個元素出現在答案中的期望機率,然後乘上元素數量即為總貢獻。
- G:注意實際意義!