2025国庆Day2
模拟赛
T1
简单题
离散化+差分即可
或者直接贪心
对可能成为答案的点计算删的区间并取min
#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 L[200005],R[200005],cnt,cn;
double p[800005],an[400005];
signed main()
{
freopen("interval.in", "r", stdin);
freopen("interval.out", "w", stdout);
int T=read();
while(T--){
int n=read();
cn=0;
for(int i=1;i<=n;i++){
L[i]=read();
R[i]=read();
an[++cn]=L[i];
an[++cn]=R[i];
}
if(n<=1){
cout<<-1<<'\n';
continue;
}
sort(L+1,L+n+1);
sort(R+1,R+n+1);
sort(an+1,an+cn+1);
cnt=0;
for(int i=1;i<=cn;i++){
p[++cnt]=1.0*an[i];
if(i<cn) p[++cnt]=(an[i]+an[i+1])/2.0;
}
int minn=n;
for(int i=1;i<=cnt;i++){
int rsu=lower_bound(R+1,R+n+1,p[i])-R-1;
int lsu=n-(upper_bound(L+1,L+n+1,p[i])-L-1);
if(rsu&&lsu){
minn=min(minn,n-rsu-lsu);
}
}
if(minn>=n) cout<<-1<<'\n';
else cout<<minn<<'\n';
}
return 0;
}
T2
结论题
容易发现一开始答案为2^(n+m-1)
因为只要第一行第一列填满,剩余就全部确定了
具体的,a[i,j]=a[1,1]a[1,j]a[i,1]
将每个点拆成1/0
对限制建边
并查集维护联通块个数即可
#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 ans,n,m,q,sec[600005];
int fa[600005],val[600005];
int find(int x){
if(fa[x]==x) return x;
int ff=find(fa[x]);
val[x]^=val[fa[x]];
fa[x]=ff;
return ff;
}
int merge(int x,int y,int c){
int fx=find(x);
int fy=find(y);
if(fx==fy){
if((val[x]^val[y])!=c) return 2;
else return 1;
}
fa[fx]=fy;
val[fx]=val[y]^val[x]^c;
return 0;
}
signed main()
{
freopen("color.in", "r", stdin);
freopen("color.out", "w", stdout);
n=read(),m=read(),q=read();
sec[0]=1;
for(int i=1;i<=n+m;i++) sec[i]=sec[i-1]*2%MOD;
ans=n+m-1;
cout<<sec[ans]<<'\n';
for(int i=1;i<=n+m;i++) fa[i]=i;
while(q--){
int x=read(),y=read(),c=read();
int zh=merge(x,y+n,c);
if(zh==2) ans=-1;
else if(!zh){
if(ans!=-1) ans--;
}
if(ans==-1) cout<<0<<'\n';
else cout<<sec[ans]<<'\n';
}
return 0;
}
T3
枚举d
注意长度为d的区间是一段前缀和后缀拼起来
枚举前缀,哈希维护相同段出现次数
计算答案时O(1)计算相同段的数量变化值
T4
不会

笛卡尔树
CF2048F
对b构建笛卡尔树
每次一定选一棵子树
注意到答案最多是 log2(1e18) ≈ 60
因此可以记 gx,k 表示 x 子树内操作了 k 次,最大值最小是多少
dp即可
树状数组
线段树
并查集
CF1713E
和T2相似
每个点拆成两个点
a[x,y] 要么呆在原地,要么跑到 a[y,x]
假设 x > y
• 若 a[x,y]>a[y,x],显然应该交换,则 k = x 或 k = y 其中之一进行操作
• 若 a[x,y]<a[y,x],显然不应该交换,则 k = x 和 k = y 要么都不操作,要么都操作
若是 a[x,y] > a[y,x],则是 (x0, y1), (x1, y0) 两对点之间连边
否则是 (x0, y0), (x1, y1) 两对点连边
贪心维护是否修改即可
trie
P4551
建二进制trie
贪心
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
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 ch[3100005][2],tot=1;
int val[100005];
void dfs(int x,int fa){
for(auto [ed,w]:tu[x]){
if(ed==fa) continue;
val[ed]=val[x]^w;
dfs(ed,x);
}
}
void ins(int val){
int son=1;
for(int i=(1<<30);i;i/=2){
bool f=(val&i);
if(!ch[son][f]){
ch[son][f]=++tot;
}
son=ch[son][f];
}
}
int query(int val){
int ans=0;
int son=1;
for(int i=(1<<30);i;i/=2){
bool f=(val&i);
if(ch[son][!f]) {
ans+=i;
son=ch[son][!f];
}
else son=ch[son][f];
}
return ans;
}
signed main(){
int n=read();
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});
}
val[1]=0;
dfs(1,0);
for(int i=1;i<=n;i++){
ins(val[i]);
}
int maxx=0;
for(int i=1;i<=n;i++){
maxx=max(maxx,query(val[i]));
}
cout<<maxx<<'\n';
return 0;
}
平衡树
数据结构优化DP
CF372C
暴力dp
单调队列优化
P9871
强制断开一天
暴力dp
线段树优化
对于一个区间(l,r,v)
转移到r时,(1,l)的位置+v的权值
前缀加线段树即可
注意n巨大
离散化后dp
数据结构二分
形如查找区间第一个比k大的值
//找到 [x,n] 中第一个比 k 大的位置
//mx[p] 表示最大值
int query(int x,int k,int p=1,int l=1,int r=n) {
if(mx[p]<=k||r<x)return r+1;
if(l==r)return l;
int mid=l+r>>1,res=query(x,k,p*2,l,mid);
if(res!=mid+1)return res;
return query(x,k,p*2+1,mid+1,r);
}
//找到前缀和中第一个大于等于 k 的位置(线段树)
//s[p] 表示和
int query(int k,int p=1,int l=1,int r=n) {
if(s[p]<k)return r+1;// 特判
if(l==r)return l;
int mid=l+r>>1;
if(s[p*2]>=k)return query(k,p*2,l,mid);
return query(k-s[p*2],p*2+1,mid+1,r);
}
//找到前缀和中第一个大于等于 k 的位置(树状数组)
int query(int k) {
int x=0;
for(int i=__lg(n);~i;i--)
if(x+(1<<i)<=n&&c[x+(1<<i)]<k)x+=1<<i,k-=c[x];
return x+1;
}
P11217
容易发现最多循环log次
枚举循环次数
对于第一次断开的循环,寻找断开的位置
相当于找前缀和第一个大于某个值的位置
可以使用线段树上二分实现
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define lson x<<1
#define rson x<<1|1
using namespace std;
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 a[1000005],sec;
struct node{
int sum;
int size;
int add;
node(){
sum=size=add=0;
}
void chu(int v){
sum=v;
size=1;
}
}tr[4000005];
node operator+(const node &l,const node &r){
node ans;
ans.sum=l.sum+r.sum;
ans.size=l.size+r.size;
return ans;
}
void color(int l,int r,int x,int v){
tr[x].add+=v;
tr[x].sum+=tr[x].size*v;
}
void pushdown(int l,int r,int x){
int mid=(l+r)>>1;
color(l,mid,lson,tr[x].add);
color(mid+1,r,rson,tr[x].add);
tr[x].add=0;
}
void build(int l,int r,int x){
if(l==r){
tr[x].chu(a[l]);
return ;
}
int mid=(l+r)>>1;
build(l,mid,lson);
build(mid+1,r,rson);
tr[x]=tr[lson]+tr[rson];
}
int query(int l,int r,int x,int k){
if(l==r) return l;
pushdown(l,r,x);
int mid=(l+r)>>1;
if(tr[lson].sum*(1ll<<(sec-1))>=k) return query(l,mid,lson,k);
return query(mid+1,r,rson,k-tr[lson].sum*(1ll<<(sec-1)));
}
void modify(int l,int r,int x,int nl,int nr,int v){
if(nl<=l&&r<=nr){
color(l,r,x,v);
return ;
}
pushdown(l,r,x);
int mid=(l+r)>>1;
if(nl<=mid) modify(l,mid,lson,nl,nr,v);
if(mid<nr) modify(mid+1,r,rson,nl,nr,v);
tr[x]=tr[lson]+tr[rson];
}
signed main()
{
//freopen("filename.in", "r", stdin);
//freopen("filename.out", "w", stdout);
int n=read(),m=read(),w=read();
for(int i=1;i<=n;i++) a[i]=read();
build(1,n,1);
while(m--){
int l=read(),r=read(),k=read();
modify(1,n,1,l,r,k);
int nw=0;
int kk=1;
for(sec=1;;sec++){
nw+=tr[1].sum*kk;
if(nw>=w){
nw-=tr[1].sum*kk;
break;
}
kk*=2;
}
int ans=query(1,n,1,w-nw);
cout<<ans+(sec-1)*n-1<<'\n';
}
return 0;
}
P6619
容易发现随着温度的增加,2类减少,1类增加
所以树状数组二分找出两条曲线的交点
注意温度离散化
扫描线
P1972
设prei表示上一个ci颜色的位置
i能贡献答案当且仅当
prei<i&&l<=i<=r
转化成扫描线查询矩阵内点的个数
P11363


(k从大到小枚举)
45度线段覆盖
将矩阵用45度线分成两个三角形
对于一条斜线变成横线(x/y,x-y)
扫描线扫x/y轴线段树维护
dfs序
子树加子树求和=区间加区间求和
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<bits/stdc++.h>
#define int long long
#define lson x<<1
#define rson x<<1|1
using namespace std;
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 v[1000005],a[1000005],dfn[1000005],cnt,siz[1000005];
struct node{
int sum;
int size;
int add;
node(){
sum=size=add=0;
}
void chu(int v){
sum=v;
size=1;
}
}tr[4000005];
node operator+(const node &l,const node &r){
node ans;
ans.sum=l.sum+r.sum;
ans.size=l.size+r.size;
return ans;
}
void color(int l,int r,int x,int v){
tr[x].add+=v;
tr[x].sum+=tr[x].size*v;
}
void pushdown(int l,int r,int x){
int mid=(l+r)>>1;
color(l,mid,lson,tr[x].add);
color(mid+1,r,rson,tr[x].add);
tr[x].add=0;
}
void build(int l,int r,int x){
if(l==r){
tr[x].chu(a[l]);
return ;
}
int mid=(l+r)>>1;
build(l,mid,lson);
build(mid+1,r,rson);
tr[x]=tr[lson]+tr[rson];
}
node query(int l,int r,int x,int nl,int nr){
if(nl<=l&&r<=nr) return tr[x];
pushdown(l,r,x);
int mid=(l+r)>>1;
if(nl<=mid){
if(mid<nr) return query(l,mid,lson,nl,nr)+query(mid+1,r,rson,nl,nr);
else return query(l,mid,lson,nl,nr);
}
else return query(mid+1,r,rson,nl,nr);
}
void modify(int l,int r,int x,int nl,int nr,int v){
if(nl<=l&&r<=nr){
color(l,r,x,v);
return ;
}
pushdown(l,r,x);
int mid=(l+r)>>1;
if(nl<=mid) modify(l,mid,lson,nl,nr,v);
if(mid<nr) modify(mid+1,r,rson,nl,nr,v);
tr[x]=tr[lson]+tr[rson];
}
vector<int> tu[1000005];
void dfs(int x,int fa){
dfn[x]=++cnt;
siz[x]=1;
for(auto ed:tu[x]){
if(ed==fa) continue;
dfs(ed,x);
siz[x]+=siz[ed];
}
}
signed main()
{
//freopen("filename.in", "r", stdin);
//freopen("filename.out", "w", stdout);
int n=read(),m=read(),rt=read();
for(int i=1;i<=n;i++) v[i]=read();
for(int i=1;i<n;i++){
int u=read(),v=read();
tu[u].push_back(v);
tu[v].push_back(u);
}
dfs(rt,0);
for(int i=1;i<=n;i++){
a[dfn[i]]=v[i];
}
build(1,n,1);
while(m--){
int opt=read(),aa=read();
if(opt==1){
int val=read();
modify(1,n,1,dfn[aa],dfn[aa]+siz[aa]-1,val);
}
else{
cout<<query(1,n,1,dfn[aa],dfn[aa]+siz[aa]-1).sum<<'\n';
}
}
return 0;
}
子树加,单点加,路径求和:
维护出du,表示1~u的路径和
单点加,相当于子树加,线段树区间加即可
子树加:
对于每个点y相当于+v*(depy-depx+1)
拆开线段树维护
杂题
推箱子
首先一定先完成t小的箱子
假设要将箱子 i 从 ai 推到 bi(假设 ai < bi),则箱子 j 会被跟着推动的条件是 j > i 且 aj < bi − i + j
若其被推动,则会移动到 bi − i + j
但这并不好维护
限制可转化成aj-j<bi-i
bi-i是定值,aj-j可线段树维护
一次移动相当于给 j ≥ i 的 j 一起对 bi + i 取 max
因为 ai + i 单调递增所以取到的是一个区间
使用线段树二分找到这个区间
找到过后计算出贡献并赋值即可
三目运算符
找规律
发现s=f(s)的串不含101,110
101操作一次即可变成100
110会将后面的一个0变成1
总是先操作101再操作110
所以答案只跟第一个110的位置(i)有关
答案是n-i+1
若只有101答案为1
若都没有答案为0
实现时线段树维护区间前/后两位数,合并时讨论一下

浙公网安备 33010602011771号