20241215北京总结
总结
做题 :
贪心:\(ACHIJ\) ; 博弈:\(AC\) ; 构造:无
授课内容 :
贪心 : 拟阵 , 反悔贪心
博弈 : SG , 非SG ( 感觉就是思维 )
构造 : 思维+1 人类智慧
未理解 :
拟阵 , 部分博弈 , 部分构造
练习题总结 : 如下
模拟赛 :
\(THUPC2025\) 初赛
\(+6 , rk360\)
\(M\) 显然 , 我开的 \(J\)
首先我们把所有黑点旁边的点叫做需保护的点 , 我们要求需保护的点旁边全都有白点 , 由于黑点不能染成白点 , 我们用黑点把树分成几份
对于每一份 , 贪心的做每一个需保护点的爹 , 但是有的点 , 它没有爹 , 怎么办呢
先处理其他的点 , 若这种没爹的点被保护就无所谓了 , 没被保护就答案加一.
后来开\(D\) , 发现 \(D\) 让我想复杂了 , 只需要直接维护到贡献开始循环就可以了 , 其中不断取最大值最小值显然平衡树 , \(set\) 实现
其他题队友做的 , 以后补 /kk
练习题总结如下
贪心
上来先做了三道反悔贪心\(/kk\)
P1484 种树
先从大到小选 , 如果只能选两个的话 , 要么选 \(a_i\) 与不和它相邻的\(a_j\) , 要么选 \(a_{i-1}\) 和 \(a_{i+1}\) , 选了 \(a_i\) 就把 \(a_{i-1}+a_{i+1}-a_i\) 入堆就行 , 维护一个链表 .
/*
* @Author: 2019yyy
* @Date: 2024-12-15 08:22:59
* @LastEditors: 2019yyy
* @LastEditTime: 2024-12-15 08:48:23
* @FilePath: \code\20241215\P1484.cpp
* @Description:
*
* I love Chtholly forever
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct Node{
int val,id;
bool operator<(Node another) const {
return val<another.val;
}
} a[1100000];
int v[1100000];
priority_queue<Node> q;
bool vis[1100000];
int nxt[1100000],pre[1100000];
signed main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i];
nxt[i]=i+1;
pre[i]=i-1;
q.push({v[i],i});
}
int ans=0,cnt=0;
while(not q.empty()){
Node now=q.top();
q.pop();
if(vis[now.id]){
continue;
}
cnt++;
if(now.val<0 or cnt>m){
break;
}
ans+=now.val;
vis[pre[now.id]]=vis[nxt[now.id]]=true;
v[now.id]=now.val=v[pre[now.id]]+v[nxt[now.id]]-now.val;
pre[now.id]=pre[pre[now.id]];
nxt[now.id]=nxt[nxt[now.id]];
pre[nxt[now.id]]=now.id;
nxt[pre[now.id]]=now.id;
q.push(now);
}
cout<<ans<<'\n';
return 0;
}
数据备份与本题思路基本相同 , 这里不过多赘述 .
[AGC008B] Contiguous Repainting
因为填色次数是无限的 , 所以你前面想怎么填怎么填 , 只要保证有一段是连续长度至少为 \(k\) 的区间就行
枚举这个区间
/*
* @Author: 2019yyy
* @Date: 2024-12-15 17:49:16
* @LastEditors: 2019yyy
* @LastEditTime: 2024-12-15 17:59:42
* @FilePath: \code\20241215\agc008b.cpp
* @Description:
*
* I love Chtholly forever
*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[1100000],sumn,summ;
signed main(){
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>=0){
sumn+=a[i];
summ+=a[i];
}
}
for(int i=1;i<=k;i++){
if(a[i]>=0){
sumn-=a[i];
}
if(a[i]<0){
summ+=a[i];
}
}
int ans=max(sumn,summ);
for(int i=k+1;i<=n;i++){
if(a[i-k]>=0){
sumn+=a[i-k];
}
if(a[i-k]<0){
summ-=a[i-k];
}
if(a[i]>=0){
sumn-=a[i];
}
if(a[i]<0){
summ+=a[i];
}
ans=max(ans,max(sumn,summ));
}
cout<<ans<<'\n';
return 0;
}
打怪兽与邻项交换法
有 \(𝑛\) 只怪兽, 初始你有 \(𝑘\) 点血量, 打第 \(𝑖\) 个怪兽至少需要 \(𝑎_𝑖\) 的血量, 打完第 \(𝑖\) 个怪兽之后会掉 \(𝑏_𝑖\) 的血量,你可以按照任何顺序依次打所有怪兽, 问能否打完所有怪兽,
并给出一种方案.
考虑打怪兽的先后顺序影响的需要最少血量数 , 如相邻 \(i\) 与 \(j\)
如果先打 \(𝑖\), 需要 \(max(𝑎_𝑖,𝑏_𝑖 + 𝑎_𝑗)\) 的血量 ;
如果先打 \(𝑗\), 需要 \(max(𝑎_𝑗,𝑏_𝑗 + 𝑎_𝑖)\) 的血量
也就是说 \(b_i+a_j\)小的优 , 这就是邻项交换
[AGC032E] Modulo Pairing
如果没有膜 \(m\) , 那么这个问题肯定是大加小
考虑两组匹配 \((x,y)\) , \((w,z)\) 且 \(y > z\) , \(w+z\geq m\)
开始最大为 \(max(x+y , w+z-m)\) ; 交换后最大为 \(max(x+z , w+y-m)\)
显然变得不劣 , \(so\) 排个序 , 以某点为分解线 , 前面的按朴素组合后面的按朴素组合
某点怎么求呢 , 这个点向左移左边肯定减少 , 右边的也减少 , 左移途径的点也减少 , 这下肯定不劣
于是就越左越好 , 但太左了不合法 , 所以在合法范围内左倾 , 容易想到二分
/*
* @Author: 2019yyy
* @Date: 2024-12-15 18:32:14
* @LastEditors: 2019yyy
* @LastEditTime: 2024-12-15 18:54:48
* @FilePath: \code\20241215\agc032e.cpp
* @Description:
*
* I love Chtholly forever
*/
#include<bits/stdc++.h>
using namespace std;
int a[2100000];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n*2;i++){
cin>>a[i];
}
sort(a+1,a+n*2+1);
int l=-1,r=n+1;
while(l+1!=r){
int mid=(l+r)>>1;
bool flag=true;
for(int i=n*2;i>=n*2-mid+1;i--){
if(a[i]+a[n*4-2*mid-i+1]<m){
flag=false;
break;
}
}
if(flag){
l=mid;
}else{
r=mid;
}
}
int ans=0;
for(int i=1;i<=n-l;i++){
ans=max(ans,a[i]+a[2*n-2*l+1-i]);
}
for(int i=n*2;i>=n*2-l+1;i--){
ans=max(ans,(a[i]+a[n*4-2*l+1-i])%m);
}
cout<<ans<<'\n';
return 0;
}
拟阵与装备购买
喜报
我不会
博弈
先 \(Nim\) ,鉴定为纯纯的打表
poj2425
就是跑一边求 \(SG\)
/*
* @Author: 2019yyy
* @Date: 2024-12-15 19:22:35
* @LastEditors: 2019yyy
* @LastEditTime: 2024-12-15 19:56:32
* @FilePath: \code\20241215\poj2425.cpp
* @Description:
*
* I love Chtholly forever
*/
#include<bits/stdc++.h>
using namespace std;
struct Edge{
int next,to;
} a[1100000];
int cnt,head[5100];
int sg[5100];
int dfs(int x){
if(sg[x]>=0){
return sg[x];
}
vector<int> v;
for(int i=head[x];i;i=a[i].next){
int to=a[i].to;
v.push_back(dfs(to));
}
sort(v.begin(),v.end());
unique(v.begin(),v.end());
int t=0;
for(int i=0;i<v.size();i++){
if(v[i]!=t){
break;
}
t++;
}
return sg[x]=t;
}
void addEdge(int x,int y){
a[++cnt].next=head[x];
a[cnt].to=y;
head[x]=cnt;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
while(cin>>n and n!=EOF){
memset(sg,-0x3f,sizeof(sg));
memset(head,0,sizeof(head));
cnt=0;
for(int i=1;i<=n;i++){
int t;
cin>>t;
for(int j=1;j<=t;j++){
int to;
cin>>to;
addEdge(i-1,to);
}
}
for(int i=0;i<=n-1;i++){
if(sg[i]<0){
dfs(i);
}
}
int m;
while(cin>>m and m!=0){
int sum=0;
for(int i=1;i<=m;i++){
int x;
cin>>x;
sum=sum^sg[x];
}
if(sum){
cout<<"WIN\n";
}else{
cout<<"LOSE\n";
}
}
}
return 0;
}
看点非 \(Nim\) 博弈 , 感觉这种旨在思维
[AGC014D] Black and White Tree
首先考虑如果 \(Alice\) 最脑瘫 , 那么她一定要下到叶子 , 之后被 \(Bob\) 堵死
\(so\) , 我们作为不脑瘫的 \(Alice\) ,肯定是下到叶子的爹上 , 让\(Bob\) 填叶子 , 他要是脑瘫不填他就输了
所以一个爹对一个叶子 , 这是一个匹配 , 只要完全匹配 \(Bob\) 必赢 , 反之 \(Alice\) 必赢
怎么求一个树是否完全匹配呢 ? 从深度最深的点开始贪心 , 选爹肯定比选儿子要好 , 不断选爹匹配就好了
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
struct Edge{
int next,to;
} a[2100000];
int cnt,head[1100000];
void addEdge(int x,int y){
a[++cnt].next=head[x];
a[cnt].to=y;
head[x]=cnt;
}
int fa[1100000],dep[1100000];
void dfs(int x,int father){
for(int i=head[x];i;i=a[i].next){
int to=a[i].to;
if(to==father){
continue;
}
fa[to]=x;
dep[to]=dep[x]+1;
dfs(to,x);
}
}
priority_queue<pii > q;
bool vis[1100000];
int main(){
int n;
cin>>n;
for(int i=1;i<=n-1;i++){
int x,y;
cin>>x>>y;
addEdge(x,y);
addEdge(y,x);
}
if(n&1){
cout<<"First\n";
return 0;
}
dep[1]=1;
dfs(1,0);
for(int i=1;i<=n;i++){
q.push(make_pair(dep[i],i));
}
vis[0]=true;
while(not q.empty()){
int now=q.top().second;
q.pop();
if(not vis[now]){
vis[now]=true;
if(vis[fa[now]]){
cout<<"First\n";
return 0;
}
vis[fa[now]]=true;
}
}
cout<<"Second\n";
return 0;
}
构造
诈骗

浙公网安备 33010602011771号