2025国庆Day6
模拟赛
T1
枚举每个点
直接对每个ai%r
再考虑区间减
判断是否有剩余即可
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
#define lson x<<1
#define rson x<<1|1
using namespace std;
int ksm(int a,int b,int p){
if(b==0) return 1;
if(b==1) return a%p;
int c=ksm(a,b/2,p);
c=c*c%p;
if(b%2==1) c=c*a%p;
return c%p;
}
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
#define lowbit(x) x&(-x)
int a[100005];
const int MAXN=1e5+5;
void modify(int l,int r,int c){
for(int i=l;i<=MAXN;i+=lowbit(i)) a[i]+=c;
for(int i=r+1;i<=MAXN;i+=lowbit(i)) a[i]-=c;
}
int query(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i)) ans+=a[i];
return ans;
}
signed main()
{
freopen("dskfj.in", "r", stdin);
freopen("dskfj.out", "w", stdout);
int n=read(),k=read(),r=read();
for(int i=1;i<=n;i++){
int x=read();
modify(i,i,x);
}
for(int i=1;i<=n-k+1;i++){
int zh=query(i)%r;
if(!zh) continue;
int l=i,r=i+k-1;
modify(l,r,-zh);
}
for(int i=1;i<=n;i++){
int zh=query(i);
if(zh%r!=0){
cout<<"No"<<'\n';
return 0;
}
}
cout<<"Yes"<<'\n';
return 0;
}
T2
树形dp
一棵子树内共四种情况
空、一条链、两条链、一个倒Y
记录倒Y的数量(a)及链的数量(b)
发现3个链自己可以组成三叉
一个倒Y也可组成三叉
一个链和一个倒Y也可组成三叉
先三个链自己配
再配倒Y
于是记录min(a,2)和b%3的值
发现可能会对父亲贡献链和倒Y
因此倒Y要记录min(a,3)
链记录0~4,注意0,3 、1,4不同,区别在于能否对父亲贡献2条链
然后树上背包就行
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
if(b==0) return 1;
if(b==1) return a%p;
int c=ksm(a,b/2,p);
c=c*c%p;
if(b%2==1) c=c*a%p;
return c%p;
}
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int dp[100005][4],f[100005][5][4],g[5][4];
vector<pair<int,int> > tu[100005];
void dfs(int x,int fa){
f[x][0][0]=0;
for(auto [ed,w]:tu[x]){
if(ed==fa) continue;
dfs(ed,x);
for(int i=0;i<5;i++){
for(int j=0;j<4;j++) g[i][j]=-1e18;
}
for(int i=0;i<5;i++){
for(int j=0;j<4;j++){
g[i][j]=max(g[i][j],f[x][i][j]+dp[ed][0]);
int nxt=j+1;
if(nxt>3) nxt=3;
g[i][nxt]=max(g[i][nxt],f[x][i][j]+w+max(dp[ed][2],dp[ed][3]));
nxt=i+1;
if(nxt==5) nxt=2;
g[nxt][j]=max(g[nxt][j],f[x][i][j]+w+max(dp[ed][0],dp[ed][1]));
}
}
for(int i=0;i<5;i++){
for(int j=0;j<4;j++){
f[x][i][j]=g[i][j];
}
}
}
for(int q=0;q<3;q++){
for(int i=0;i<5-q;i++){
int st=i%3;
for(int j=st;j<4;j++){
dp[x][q]=max(dp[x][q],f[x][i+q][j]);
}
}
}
for(int i=0;i<5;i++){
int st=i%3;
for(int j=st;j<3;j++){
dp[x][3]=max(dp[x][3],f[x][i][j+1]);
}
}
}
signed main()
{
freopen("ternary.in", "r", stdin);
freopen("ternary.out", "w", stdout);
int T=read();
while(T--){
int n=read();
for(int i=1;i<=n;i++) tu[i].clear();
for(int i=1;i<=n;i++){
for(int j=0;j<5;j++){
for(int k=0;k<4;k++){
dp[i][k]=f[i][j][k]=g[j][k]=-1e18;
}
}
}
for(int i=1;i<n;i++){
int u=read(),v=read(),w=read();
tu[u].push_back({v,w});
tu[v].push_back({u,w});
}
dfs(1,0);
cout<<dp[1][0]<<'\n';
}
return 0;
}
T3

另一种思路
前半部分是一样的
分 <=e/2 和 >e/2
第一组内部的相对顺序不能改变
从小到大加入第二组中的所有数
设当前加入了 x
称一个点 a[i] 是断点,当:
- 它属于第一组,同时 a[i]+x<=e
- 它属于第二组,同时已经被加入了
第一种称为第一类断点
整个序列被第一类断点分割成若干段
找到 x 所在的那一段 [l,r]
那么,x 可以被放到 [l,r] 中的任意一个位置
同时,不能被放到 [l,r] 以外,因为边界处是不可交换的断点
考虑所有 [l,r] 中的断点,一个计数经典技巧是,只考虑 x 后面第一个断点是什么
将答案乘上为 x 选取后面第一个断点的方案数
然后标记 x 为断点
整个过程都可以 set 维护断点分割的连续段
就是钦定断点之间的相对位置不能被改变(无论第一类还是第二类)
那么,x 可以被插入到两个断点之间
这个方案数就是 (l,r] 中的断点数量
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
if(b==0) return 1;
if(b==1) return a%p;
int c=ksm(a,b/2,p);
c=c*c%p;
if(b%2==1) c=c*a%p;
return c%p;
}
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const int MOD=1e9+7;
int a[200005],cnmin,cnmax;
pair<int,int> minn[200005],maxx[200005];
set<pair<int,int> > p;
signed main()
{
freopen("str.in", "r", stdin);
freopen("str.out", "w", stdout);
int n=read(),E=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
for(int i=1;i<=n;i++){
if(a[i]<=E/2){
minn[++cnmin]={a[i],i};
p.insert({i,1});
}
else maxx[++cnmax]={a[i],i};
}
p.insert({n+1,1});
sort(minn+1,minn+cnmin+1);
reverse(minn+1,minn+cnmin+1);
sort(maxx+1,maxx+cnmax+1);
// for(int i=1;i<=cnmin;i++) cout<<minn[i].first<<' ';
// cout<<'\n';
int ans=1;
int l=1;
for(int i=1;i<=cnmax;i++){
while(minn[l].first+maxx[i].first>E&&l<=cnmin){
auto pp=p.lower_bound({minn[l].second,-1});
pair<int,int> px=*pp;
pp++;
pair<int,int> py=*pp;
int id=py.first,sum=px.second+py.second;
// cout<<id<<' '<<sum<<'\n';
p.insert({id,sum});
p.erase(px);
p.erase(py);
l++;
}
auto pp=p.lower_bound({maxx[i].second,-1});
pair<int,int> px=*pp;
ans*=px.second;
ans%=MOD;
p.erase(px);
p.insert({px.first,px.second+1});
}
cout<<ans<<'\n';
return 0;
}
T4
预处理极小mex区间
变成三维偏序
观察性质

变成二位偏序
贪心模拟二分
1.https://www.luogu.com.cn/problem/AT_kupc2018_k
容易发现不好点一定是独立集(否则有边一定有一个点是好点)
发现K没用,直接变成K=2即可
然后将平均值拆开
check(m)是否可行
等价于b1-m + b2-m + …… + bq-m >=0
变成求权值和最大的点覆盖
(点覆盖就是独立集的补集)
转化成求独立集的最小值
可以dp
dpr表示右边选r这个点的最小独立集
枚举上一个选的点l,计算区间贡献K_l,r
每次从r到r+1时,对1~l-1的区间的K_p,r+1进行一个w_l,r的区间加,同时修改dp_r+1
需要线段树维护区间加,全局min,单点修
2.https://www.luogu.com.cn/problem/P9695
对每个x二分一个左端点l右端点r
数据结构check是否可行(s_a1+s_a2+……+s_ak是否=r-l+1)
3.

考虑贪心
让每个ai尽量大,此基础上让bi尽量大
但不一定优
考虑对ai做前缀减(相当于区间减)
假设减去h
于是我们就有了h次区间加的操作
贪心加区间长的即可
4.https://www.luogu.com.cn/problem/CF405E
对于一棵树从下往上贪心
找到一个父亲节点,使得所有儿子都是叶子
若儿子为偶数,配对消除
否则,剩下的一条边与fa-fa[fa]这条边配对,消除一棵子树
可以自然转化成图
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define jiaa(a,b) {a+=b;if(a>=MOD) a-=MOD;}
#define jian(a,b) {a-=b;if(a<0) a+=MOD;}
using namespace std;
int ksm(int a,int b,int p){
if(b==0) return 1;
if(b==1) return a%p;
int c=ksm(a,b/2,p);
c=c*c%p;
if(b%2==1) c=c*a%p;
return c%p;
}
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
vector<pair<int,int> > tu[100005];
int vis[100005];
int dfs(int x){
queue<int> q;
for(auto [ed,id]:tu[x]){
if(vis[id]) continue;
vis[id]=1;
int f=dfs(ed);
if(f) cout<<x<<' '<<ed<<' '<<f<<'\n';
else q.push(ed);
}
// cout<<q.size()<<'\n';
while(q.size()>=2){
cout<<q.front()<<' ';
cout<<x<<' ';
q.pop();
cout<<q.front()<<'\n';
q.pop();
}
if(q.size()) return q.front();
return 0;
}
signed main()
{
//freopen("filename.in", "r", stdin);
//freopen("filename.out", "w", stdout);
int n=read(),m=read();
if(m%2==1){
cout<<"No solution"<<'\n';
return 0;
}
for(int i=1;i<=m;i++){
int u=read(),v=read();
tu[u].push_back({v,i});
tu[v].push_back({u,i});
}
dfs(1);
return 0;
}
5.https://www.luogu.com.cn/problem/P11189
先进行操作2
最后一定使得ap<=0且ap<=afa才能通过操作1满足要求
于是每个叶子节点的操作次数区间[li,ri]易求
考虑如何快速求父亲的[li,ri]
li就是所有儿子的li的max
ri发现满足单调性,二分check
6.https://www.luogu.com.cn/problem/P13552
容易发现最后进行加操作一定是优的
问题转化成将一堆数划分成k个集合,使得每个集合按位与结果最大
假设有m个数最高位=1
首先有一些性质
二进制下A 1 B 0 C 0
则 (a&b)+c<a+(b&c)
于是,若k>=m+1,则每个高位1单独一个集合,剩余的0划分成k-m个集合
否则,将所有的0按位与进行合并,递归考虑次高位的情况

浙公网安备 33010602011771号