目录
写在前面
补题地址: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 中间过了三道题没做出来真唐吧
最大数量即尽量使所有字符串没有公共前缀,即尽可能使每一层的节点均填满。则答案即为:
\[\sum_{i=0}^{m} \min(n, 26^i) \]考虑期望。插入的每个字符串长度均为 \(m\),则每个字符串都会占据 \(1\sim m\) 层的一个节点。即对于建出来的字典树,第 \(i\) 层上的所有节点,一定是对应了这 \(n\) 个字符串的长度为 \(i\) 的前缀。又字符串随机构造,则相当于在第 \(i\) 层上随机选择了 \(n\) 个节点,并求随机选择节点的数量。
考虑每一个节点对这一层的贡献再乘上本层总数即为本层贡献。考虑反面,求该节点没有被选择的概率(相当于限制仅能从 \(26^i - 1\) 个节点中独立地随便选 \(n\) 个),则可知期望的总数为:
\[\sum_{i=0}^{m} \left[ 1 - \left(\frac{26^i - 1}{26^i}\right)^n \right] 26^i \]#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:注意实际意义!