比赛传送门
作者: fn
题目大意
给定一个具有 n n n 个节点的树,保证 n n n 是偶数。
您将删除一些边(至少1条),并且必须让每个剩余的连通块具有偶数个顶点。
计算删除的方法数,模998244353。
考察内容
dfs,复杂度优化
分析
除了根节点,每一个偶数大小的子树可以带来一条可以切的边。
预处理子树大小,顺便统计偶数大小子树的个数 c n t cnt cnt ,
答案为 2 c n t − 1 − 1 2^{cnt-1}-1 2cnt−1−1
用O3和快读优化一下。
#pragma GCC optimize(3)
#include
#define ll long long
#define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+3;
const ll mod=998244353;
ll read(ll &n){
char ch=' '; ll q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
n=q*w; return n;
}
ll power(ll a,ll b){ // a^b%mod
ll c=1;
for(;b;b>>=1){
if(b&1)c=c*a%mod;
a=a*a%mod;
}
return c;
}
ll n;
vector<ll> g[N];
ll sz[N]; // 子树大小
ll cnt=0;
ll dfs(ll x,ll fa){ // 预处理子树大小,并统计偶数大小子树的个数
sz[x]=1;
for(auto a1:g[x]){ // 枚举子树
if(a1==fa)continue; // 跳过父亲结点
sz[x]+=dfs(a1,x);
}
if(sz[x]%2==0){ // 子树大小为偶数
cnt++; // 统计偶数大小子树的个数
}
return sz[x];
}
void init(ll n){
memset(sz,0,sizeof(sz[0])*(n+1));
for(int i=0;i<=n;i++){
g[i].clear();
}
}
signed main(){
int t; read(t);
while(t--){ // t<=30
read(n);
init(n); // 初始化
ll u,v;
for(int i=1;i<=n-1;i++){ // 读入一棵树
read(u); read(v);
g[u].push_back(v);
g[v].push_back(u);
}
cnt=0;
ll temp=dfs(1,0); // 预处理子树大小,并统计偶数大小子树的个数
cnt--; // 可以删的点的数量为cnt-1
ll ans=(power(2,cnt)%mod+mod-1)%mod;
cout<<ans<<endl;
}
return 0;
}
/*
1
4
1 2
1 3
1 4
*/
题目大意
给定一个长度为 n n n 的整数数组 b b b ,
每次操作可以使 b b b 中的一个元素+1或-1,希望使阵列呈"波浪状"。
计算使序列波浪状的最小操作数。
考察内容
动态规划,贪心
分析
线性dp。
不难发现,要变成波浪形,要么先上升再下降,要么先下降再上升。在两个答案中取最小值即可。
状态:
f [ N ] : f[N] : f[N]: 先上升再下降,前 i i i 个的答案
g [ N ] : g[N] : g[N]: 先下降再上升,前 i i i 个的答案
边界:
f [ 1 ] = 0 f[1]=0 f[1]=0
g [ 1 ] = 0 g[1]=0 g[1]=0
只有1个时不需要操作
转移:
转移的同时贪心一下,调整当前数字的大小。
for(int i=2;i<=n;i++){
f[i]=f[i-1];
g[i]=g[i-1];
if(i%2==1){ // 奇数个,f下降,g上升
if(a[i]>=a[i-1]){
f[i]+=a[i]-a[i-1]+1;
a[i]=a[i-1]-1; // 往下移
}
if(b[i]<=b[i-1]){
g[i]+=b[i-1]-b[i]+1;
b[i]=b[i-1]+1;
}
}
else{ // 偶数个,f上升,g下降
if(a[i]<=a[i-1]){
f[i]+=a[i-1]-a[i]+1;
a[i]=a[i-1]+1;
}
if(b[i]>=b[i-1]){
g[i]+=b[i]-b[i-1]+1;
b[i]=b[i-1]-1; // 往下移
}
}
}
完整代码:
#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
ll b[N];
ll f[N]; // 先上升再下降,前i个的答案
ll g[N]; // 先下降再上升,前i个的答案
int main(){ // dp
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t; cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++){
cin>>b[i];
a[i]=b[i];
}
f[1]=0; // 只有1个时不需要操作
g[1]=0;
for(int i=2;i<=n;i++){
f[i]=f[i-1];
g[i]=g[i-1];
if(i%2==1){ // 奇数个,f下降,g上升
if(a[i]>=a[i-1]){
f[i]+=a[i]-a[i-1]+1;
a[i]=a[i-1]-1; // 往下移
}
if(b[i]<=b[i-1]){
g[i]+=b[i-1]-b[i]+1;
b[i]=b[i-1]+1;
}
}
else{ // 偶数个,f上升,g下降
if(a[i]<=a[i-1]){
f[i]+=a[i-1]-a[i]+1;
a[i]=a[i-1]+1;
}
if(b[i]>=b[i-1]){
g[i]+=b[i]-b[i-1]+1;
b[i]=b[i-1]-1; // 往下移
}
}
}
cout<<min(f[n],g[n])<<endl;
}
return 0;
}
/*
1
6
1 1 4 5 1 4
4
1
3
1 2 100
输出:
2
// 1->3, 3 2 100即可
*/
题目大意
有一个纸条,分为 n n n 个空白网格。
Alice和Bob轮流行动。 每次将剩余的一个空白网格涂成黑色,同时保证没有两个黑色网格相邻。
当一名玩家无法绘制任何网格时,游戏结束。艾丽丝想要绘制格子最少,而鲍勃想要绘制格子最多。
给定 n n n 和先手方,找出两人最佳发挥时的最终得分。
考察内容
博弈论
分析
Alice 的一种最优策略是:选某个连续段的左数第二个格子涂黑;
Bob 的一种最优策略是:选某个连续段的左数第三个格子涂黑。
所以可以推得,每7个格子必定有3个被涂黑。剩下的几个格子分类讨论一下即可。
#pragma GCC optimize(3)
#include
#define ll long long
#define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
int t,n;
string s;
int sum = 0;
signed main(){
cin>>t;
while(t--){
cin>>n;
cin>>s;
sum = 0;
int num1 = n/7;
sum+=num1*3;
int num2 = n%7;
if(s=="Alice"){
if(num2>=6) sum+=3;
else if(num2>=4) sum+=2;
else if(num2>=1) sum++;
}
else{ // Bob先手
if(num2>=5) sum+=3;
else if(num2>=3)sum+=2;
else if(num2>=1)sum++;
}
cout<<sum<<endl;
}
}
/*
1
5 Bob
*/
题目大意
有 n n n 人,其中有 m m m 对朋友。目前,每个人都在帽子上写一个整数。
每一轮游戏中,每个人都用自己和所有朋友的平均号码替换帽子上的号码。
请注意,数字可能会变成非整数。
可以证明,通过玩越来越多的游戏,每个数字最终都收敛到某个值。
给定写在帽子上的初始数字,计算这些最终值。
考察内容
并查集,找规律,复杂度优化
分析
先写个暴力找规律。
规律:
用O3和快读/scanf优化一下。
#pragma GCC optimize(3)
#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
int read(int &n){
char ch=' '; int q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
n=q*w; return n;
}
ll read(ll &n){
char ch=' '; ll q=0,w=1;
for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
if(ch=='-')w=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
n=q*w; return n;
}
const int N=1e5+5;
ll n,m;
ll a[N];
ll sum[N]; //
ll sz[N]; // 边数+1求和
int fa[N];
vector<ll> g[N]; // 记录无向图
void init(ll n) { // 初始化
for (int i = 0; i <= n; i++){
g[i].clear(); // 清空g (勿忘!)
fa[i]=i;
sum[i]=0;
sz[i]=0;
}
}
int get(int x) { // 查找节点集合
if(x==fa[x])return x;
return fa[x]=get(fa[x]); // 路径压缩
}
void unionNode(int x, int y) { // 合并节点
fa[get(x)] = get(y);
}
ll sum0[N];
int main(){ // AC
int t; read(t);
while(t--){
read(n); read(m);
init(n); // 初始化
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
int u,v;
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
unionNode(u,v); // 合并
}
for(int i=1;i<=n;i++){
ll d1=g[i].size()+1;
int x=get(i);
sum[x]+=a[i]*d1;
sz[x] += d1;
}
for(int i=1;i<=n;i++){
int x=get(i);
printf("%.6f\n",sum[x]*1.0/sz[x]);
}
}
return 0;
}
题目大意
举行由 n n n 名参赛者组成的锦标赛。玩家编号从 1 1 1 到 n n n 。每场比赛都在两名参赛者之间进行,没有平局。赢得最多比赛的参与者赢得整个锦标赛;如果有多个参赛者在第一名并列,他们都会赢得比赛。
在当前状态下,一些比赛已经结束,而其他比赛尚未开始。您将获得所有已结束匹配的结果。
确定玩家1是否有可能赢得比赛。
考察内容
网络流,最大流
分析
先让 1 1 1 号选手赢下所有和他有关的比赛。
设此时选手 i i i 赢了 a i a_i ai 场比赛。
如果存在某个 a i > a 1 a_i>a_1 ai>a1 则 1 1 1 号选手不可能成为冠军。否则选手 i i i 至多还能再赢 a i − a i a_i-a_i ai−ai 场比赛。
建立一张网络流图:每场未进行的比赛在图中用一个点表示,源点向它连容量为 1 1 1 的边,它向它的两个参赛选手的对应点各自连容量为 1 1 1 的边;选手 i i i 的对应点向汇点连容量为 a i − a i a_i-a_i ai−ai 的边。
计算该图最大流,若源点出发的边满流则答案为 YES ,否则为 NO 。
最大流模板题:洛谷P3376