博弈论 - 格子涂色
题意
见代码
思路
见代码
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
// 博弈论
// 一行有n个位置 A和B轮流涂色 相邻两个位置不可以都涂色 直到某人不可涂色 游戏结束
// A想让涂色的格子数量尽可能少 B想让涂色格子数量尽可能多
// 两个人都采取最优策略 给定格子数n以及先手是谁 问游戏结束时涂色的格子数量是多少?
// A由于要尽可能使得涂色少 即通过A的涂色 卡死一些格子 让这些格子不可涂
// B要尽可能多 那就是B要使得有一些格子必须涂, 即B涂完某个位置, 让这个位置使得另外的格子最终也会涂色
// 从端点前面几个位置开始分析
// 1 2 3 .... 假如 A先涂色 那么A一定会涂2 这样1 3都没办法涂
// 假如B先涂色 那么B一定涂在3 这样可以使得1必须涂
// A先手的情况
// 一开始涂第二格 使得两边不能涂 1 A 3
// 然后B会涂在6使得4必须涂 1 A 3 4 5 B
// 然后A会涂在9使得8不能涂 1 A 3 4 5 B 7 8 A
// 然后B会涂在13使得11必须涂 1 A 3 4 5 B 7 8 A 10 11 12 13
// ..............出现了循环 即 0 A 0 X 0 B 0 一个循环 即每7位 都有3个位置一定会涂色
// 考虑剩下的长度即可
// 0 A 0 X 0 B 0 K1 K2 K3 K4 K5 K6
// 剩下1: 答案就是 n/7 * 3 + 1 A涂了K1
// 剩下2: 答案就是 n/7 * 3 + 1 最后A会涂在K2 使得K1不能涂
// 剩下3: 答案就是 n/7 * 3 + 1 和上面一样 A涂在K2 使得K1 K3 不能涂
// 剩下4: 答案就是 n/7 * 3 + 2 还是选择涂在K2 但是K4会被B涂
// 剩下5: 答案就是 n/7 * 3 + 2 还是A涂在K2 B涂在4或者5都一样
// 剩下6: 答案就是 n/7 * 3 + 3 A涂K2 B涂K6使得K4必须涂
// B先手的情况
// 一开始涂3 使得 1 必须涂, 即 1 2 B 4 5 6 7 8 9 10
// 然后A涂在6使得5不能涂 1 2 B 4 5 A 7 8 9 10
// 然后B涂在10使得8必须涂 1 2 B 4 5 A 7 8 9 B
// 然后 1 2 B 4 5 A 7 8 9 B 11 12 A 14 ....
// 循环体: X 0 B 0 0 A 0 同样是每7个都会有 3个位置涂色
// 同样考虑剩下的 X 0 B 0 0 A 0 K1 K2 K3 K4 K5 K6
// 剩1: B涂在K1 +1
// 剩2: B涂在K1 +1
// 剩3: B涂在K3使得K1必须涂 +2
// 剩4: B涂在K3使得K1必须涂 +2
// 剩5: B涂在K3使得K1必须涂 然后A要涂在K5 +3
// 剩6: B涂在K3使得K1必须涂 然后A涂在K6使得K5不能涂 +3
// 我们还发现 两种循环体分别是 0 A 0 X 0 B 0 和 X 0 B 0 0 A 0
// 由于两种情况最后一位都没有填 所以前面的循环体对于后面剩余的怎么填没有任何影响
// 即我们对剩余的分析过程 又可以恰好用于 对 n<7 时候的分析
ll n, res;
int main()
{
int t;
cin>>t;
string name;
while(t--){
cin>>n>>name;
res = n / 7 * 3;
if(n % 7 == 0){
cout<<res<<endl;
continue;
}
if(name == "Alice"){
if(n % 7 <= 3) res += 1;
else if(n % 7 <= 5) res += 2;
else res += 3;
}
if(name == "Bob"){
if(n % 7 <= 2) res += 1;
else if(n % 7 <= 4) res += 2;
else res += 3;
}
cout<<res<<endl;
}
}
树DFS,快速幂
题意
见代码
思路
见代码
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
const int mod = 998244353;
int res, n;
int sz[N];
int h[N], ne[N*2], e[N*2], idx;
// 题意 : n为偶数 给定n个结点 以及n-1条边 构成一棵树 问:有几种删法,可以删掉某些边使得, 剩下的子连通图的顶点数量都是偶数
// 对于某个结点 只要以他为根的子树结点数量是偶数 ,他头上的边一定可以删掉, 根节点除外
// 假如子树结点数是偶数的点有res个 所以最后答案就是 2^(res-1) - 1 减1因为:全都不删不是一种选择
int qmi(int a, int zhi)
{
int ans = 1;
while(zhi){
if(zhi & 1) ans = (ll)ans * a % mod;
zhi >>= 1;
a = (ll)a*a % mod;
}
return ans % mod;
}
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int i, int fi)
{
sz[i] = 1;
for(int t = h[i]; t != -1; t = ne[t]){
int j = e[t];
if(fi == j) continue;
dfs(j, i);
sz[i] += sz[j];
}
if(sz[i] % 2 == 0) res++;
}
int T;
int main()
{
scanf("%d",&T);
while(T--){
memset(h, -1, sizeof(h));
idx = 0;
scanf("%d", &n);
for(int i = 0; i<n-1; i++){
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
res = 0;
dfs(1, 0);
cout<< (qmi(2, res-1) - 1 + mod) % mod<<endl; // 要除去不删的一种情况
}
}
贪心
题意
给定一个数组,你可以将任意一个元素+1或者-1无限次,使得数组成为波浪型,即升降交替,问最小操作次数。
思路
从结果分析,最终结果是波浪型,也就是奇数位置为峰,偶数位置为谷,或者是偶峰奇谷。对于每一种情况,我们去遍历原数组,一旦违反了峰谷规则,就对当前元素进行修改, 最后两种情况的修改次数取最小值即可。
代码
略