NOI2022 题解
upd:树上邻域数点如果
[NOI2022] 众数
首先看到题面第一句话发现这玩意跟摩尔投票一样。然后我们知道摩尔投票有结合律。于是我们如果不考虑前两个操作,那每个序列开个动态开点线段树维护一下摩尔投票,然后合并可以线段树合并,查询直接查每个序列摩尔投票的和,然后再统计一下这个答案在所有序列里的出现次数是否合法即可。
对于前两个操作,可以开个什么东西动态地维护序列,然后合并可以启发式合并。
然后是坑:首先这题赛时爆了一车零,因为 1e6 个 deque 会 MLE。需要 list。
然后是这题赛时一车人挂了 0-30 不等,因为查询的序列可能相同,因此统计次数要开 long long。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <list>
using namespace std;
int n,q;
list<int>v[1000010];
struct data{
int val;
long long cnt;
data operator+(data s){
if(val==s.val)return {val,cnt+s.cnt};
else{
if(cnt>s.cnt)return {val,cnt-s.cnt};
else return {s.val,s.cnt-cnt};
}
}
};
struct node{
#define lson tree[rt].ls
#define rson tree[rt].rs
int ls,rs;
data val;
}tree[1000010<<5];
int t,rt[1000010];
void pushup(int rt){
tree[rt].val=tree[lson].val+tree[rson].val;
}
void update(int &rt,int L,int R,int pos,int val){
if(!rt)rt=++t;
if(L==R){
tree[rt].val.val=pos;
tree[rt].val.cnt+=val;
return;
}
int mid=(L+R)>>1;
if(pos<=mid)update(lson,L,mid,pos,val);
else update(rson,mid+1,R,pos,val);
pushup(rt);
}
int query(int rt,int L,int R,int pos){
if(!rt)return 0;
if(L==R)return tree[rt].val.cnt;
int mid=(L+R)>>1;
if(pos<=mid)return query(lson,L,mid,pos);
else return query(rson,mid+1,R,pos);
}
int merge(int x,int y,int l,int r){
if(!x||!y)return x|y;
if(l==r){
tree[x].val.cnt+=tree[y].val.cnt;
return x;
}
int mid=(l+r)>>1;
tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid);
tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r);
pushup(x);
return x;
}
int main(){
scanf("%d%d",&n,&q);int lim=n+q;
for(int i=1;i<=n;i++){
int l;scanf("%d",&l);
while(l--){
int x;scanf("%d",&x);
v[i].push_back(x);update(rt[i],1,lim,x,1);
}
}
while(q--){
int od;scanf("%d",&od);
if(od==1){
int x,y;scanf("%d%d",&x,&y);
v[x].push_back(y);
update(rt[x],1,lim,y,1);
}
else if(od==2){
int x;scanf("%d",&x);
int y=v[x].back();v[x].pop_back();
update(rt[x],1,lim,y,-1);
}
else if(od==3){
data val={0,0};
int m;scanf("%d",&m);
long long cnt=0,sum=0;
static int tmp[500010];
for(int i=1;i<=m;i++){
scanf("%d",&tmp[i]);
val=val+tree[rt[tmp[i]]].val;
sum+=v[tmp[i]].size();
}
for(int i=1;i<=m;i++)cnt+=query(rt[tmp[i]],1,lim,val.val);
if((cnt<<1)<=sum)puts("-1");
else printf("%d\n",val.val);
}
else{
int x1,x2,x3;scanf("%d%d%d",&x1,&x2,&x3);
rt[x1]=merge(rt[x1],rt[x2],1,lim);rt[x3]=rt[x1];
if(v[x1].size()<v[x2].size()){
while(!v[x1].empty())v[x2].push_front(v[x1].back()),v[x1].pop_back();
swap(v[x2],v[x3]);
}
else{
while(!v[x2].empty())v[x1].push_back(v[x2].front()),v[x2].pop_front();
swap(v[x1],v[x3]);
}
}
}
return 0;
}
[NOI2022] 移除石子
一道玄学的题目。
首先考虑如何判定。先看
观察到操作
设
考虑
然后是关于计数。先对
把两个东西拼起来,
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
using namespace std;
const int mod=1000000007;
int n,k,num,ans,trans[10010][7],dp[2][10010],l[1010],r[1010];
map<vector<int>,int>mp;
int dfs(vector<int>g){
if(mp.find(g)!=mp.end())return mp[g];
mp[g]=num++;int id=mp[g];
for(int i=0;i<7;i++){
vector<int>tmp;
for(int j=0;j<9;j++)tmp.push_back(101);
for(int j=0;j<3;j++){
for(int k=0;k<3;k++){
if(g[3*j+k]==101)continue;
for(int x=0;x<3;x++){
int val=i-j-k-x;
if(val<0)val=-val;
else val=val==1;
for(int l=k;l<=min(2,j+k);l++)tmp[3*l+x]=min(tmp[3*l+x],g[3*j+k]+val);
}
}
}
trans[id][i]=dfs(tmp);
}
return id;
}
int main(){
int tim;scanf("%d",&tim);
dfs({0,101,101,101,101,101,101,101,101});
while(tim--){
scanf("%d%d",&n,&k);ans=0;
for(int i=1;i<=n;i++)scanf("%d%d",&l[i],&r[i]);
memset(dp,0,sizeof(dp));dp[0][0]=1;
int cur=0;
for(int i=1;i<=n;i++){
cur^=1;
memset(dp[cur],0,sizeof(dp[cur]));
for(int j=0;j<num;j++){
if(!dp[cur^1][j])continue;
for(int k=0;k<7;k++){
int ret=0;
if(k<6)ret=l[i]<=k&&k<=r[i];
else if(r[i]>=6)ret=r[i]-max(l[i],6)+1;
dp[cur][trans[j][k]]=(dp[cur][trans[j][k]]+1ll*ret*dp[cur^1][j])%mod;
}
}
}
for(pair<vector<int>,int>p:mp)if(p.first[0]<=k)ans=(ans+dp[cur][p.second])%mod;
if(k==1){
bool jud=true;
for(int i=1;i<=n;i++)if(l[i]!=0){
jud=false;break;
}
if(jud)ans=(ans-1+mod)%mod;
if(n==3&&l[1]<=1&&r[1]>=1&&l[2]<=1&&r[2]>=1&&l[3]<=1&&r[3]>=1)ans=(ans-1+mod)%mod;
}
printf("%d\n",ans);
}
return 0;
}
[NOI2022] 树上邻域数点
不会 Top Tree。
但是你要是在笔试之前能给我点够 50 踩也不是不可以会。
[NOI2022] 挑战 NPC Ⅱ
看到
我们顺序搜两棵树,然后哈希值相同的直接匹配显然是最优的,哈希值不同的可以
然后还需要加上一些玄学剪枝:首先如果
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
typedef unsigned long long ull;
int n1,n2;
ull nxt(ull x){
x^=x<<13;x^=x>>7;x^=x<<17;
return x;
}
ull hs[100010][2];
vector<int>g[100010][2];
bool cmp1(int a,int b){
return hs[a][0]<hs[b][0];
}
bool cmp2(int a,int b){
return hs[a][1]<hs[b][1];
}
int size[100010][2];
void dfs1(int x,int id){
hs[x][id]=size[x][id]=1;
for(int v:g[x][id]){
dfs1(v,id);
hs[x][id]+=nxt(hs[v][id]);
size[x][id]+=size[v][id];
}
if(id==0)sort(g[x][id].begin(),g[x][id].end(),cmp1);
else sort(g[x][id].begin(),g[x][id].end(),cmp2);
}
bool dfs(int x,int y){
if(g[x][0].size()<g[y][1].size())return false;
if(size[x][0]<size[y][1])return false;
if(size[x][0]==size[y][1])return hs[x][0]==hs[y][1];
vector<int>A,B;
int a=0,b=0;
while(a<g[x][0].size()&&b<g[y][1].size()){
if(hs[g[x][0][a]][0]==hs[g[y][1][b]][1])a++,b++;
else if(hs[g[x][0][a]][0]<hs[g[y][1][b]][1])A.push_back(g[x][0][a]),a++;
else B.push_back(g[y][1][b]),b++;
}
while(a<g[x][0].size())A.push_back(g[x][0][a]),a++;
while(b<g[y][1].size())B.push_back(g[y][1][b]),b++;
vector<int>p;
for(int i=0;i<A.size();i++)p.push_back(i);
do{
bool jud=true;
for(int i=0;i<B.size();i++){
if(size[A[p[i]]][0]<size[B[i]][1]){
jud=false;break;
}
}
if(!jud)continue;
for(int i=0;i<B.size();i++){
if(!dfs(A[p[i]],B[i])){
jud=false;break;
}
}
if(jud)return true;
}while(next_permutation(p.begin(),p.end()));
return false;
}
int main(){
int tim;scanf("%*d%d%*d",&tim);
while(tim--){
scanf("%d",&n1);
int rt1,rt2;
for(int i=1;i<=n1;i++){
int f;scanf("%d",&f);
if(f==-1)rt1=i;
else g[f][0].push_back(i);
}
scanf("%d",&n2);
for(int i=1;i<=n2;i++){
int f;scanf("%d",&f);
if(f==-1)rt2=i;
else g[f][1].push_back(i);
}
dfs1(rt1,0);dfs1(rt2,1);
if(dfs(rt1,rt2))puts("Yes");
else puts("No");
for(int i=1;i<=n1;i++)g[i][0].clear();
for(int i=1;i<=n2;i++)g[i][1].clear();
}
}
[NOI2022] 冒泡排序
首先显然可以让所有数只取到给定的
先看 B 部分分。确定了一堆单点,那么开个区间加查最小值的线段树随便扫一扫就行了。
然后是 C。容易发现把区间的最小值放在开头一定最优,于是变成了确定一堆单点,每个位置
最后是比较困难的 A。根据上边的性质,我们必须把
于是正解就十分显然了:我们把
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
int n,m,lsh[1000010];
long long ans;
struct ques{
int l,r,val;
}q[1000010];
vector<pair<int,int> >v[1000010];
struct BIT{
int c[1000010];
#define lowbit(x) (x&-x)
void update(int x){
while(x)c[x]++,x-=lowbit(x);
}
int query(int x){
int sum=0;
while(x<=m)sum+=c[x],x+=lowbit(x);
return sum;
}
}c;
int fa[1000010];
int find(int x){
return x==fa[x]?fa[x]:fa[x]=find(fa[x]);
}
struct node{
#define lson rt<<1
#define rson rt<<1|1
int mn,pos,lz;
}tree[4000010];
void pushup(int rt){
tree[rt].mn=min(tree[lson].mn,tree[rson].mn);
if(tree[rt].mn==tree[lson].mn)tree[rt].pos=tree[lson].pos;
else tree[rt].pos=tree[rson].pos;
}
void pushtag(int rt,int val){
tree[rt].mn+=val;tree[rt].lz+=val;
}
void pushdown(int rt){
if(tree[rt].lz){
pushtag(lson,tree[rt].lz);
pushtag(rson,tree[rt].lz);
tree[rt].lz=0;
}
}
void build(int rt,int l,int r){
tree[rt].mn=tree[rt].lz=0;tree[rt].pos=l;
if(l==r)return;
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
}
void update(int rt,int L,int R,int l,int r,int val){
if(l<=L&&R<=r){
pushtag(rt,val);return;
}
pushdown(rt);
int mid=(L+R)>>1;
if(l<=mid)update(lson,L,mid,l,r,val);
if(mid<r)update(rson,mid+1,R,l,r,val);
pushup(rt);
}
pair<int,int> query(int rt,int L,int R,int l,int r){
if(l<=L&&R<=r)return make_pair(tree[rt].mn,tree[rt].pos);
pushdown(rt);
int mid=(L+R)>>1;
pair<int,int>val=make_pair(0x3f3f3f3f,0x3f3f3f3f);
if(l<=mid)val=min(val,query(lson,L,mid,l,r));
if(mid<r)val=min(val,query(rson,mid+1,R,l,r));
return val;
}
int val[1000010],mn[1000010];
void solve(){
scanf("%d%d",&n,&m);ans=0;
for(int i=1;i<=n+1;i++)fa[i]=i,val[i]=mn[i]=0;
for(int i=1;i<=m;i++)c.c[i]=0,v[i].clear();
for(int i=1;i<=m;i++)scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].val),lsh[i]=q[i].val;
sort(lsh+1,lsh+m+1);
for(int i=1;i<=m;i++){
q[i].val=lower_bound(lsh+1,lsh+m+1,q[i].val)-lsh;
v[q[i].val].push_back(make_pair(q[i].l,q[i].r));
}
for(int i=m;i>=0;i--){
if(v[i].empty())continue;
sort(v[i].begin(),v[i].end());
int pre=n+1;
for(int j=v[i].size()-1;j>=0;j--){
if(v[i][j].second<pre){
int pos=find(v[i][j].first);
if(pos>v[i][j].second){
puts("-1");return;
}
val[pos]=i;pre=pos;
}
}
for(pair<int,int>p:v[i]){
for(int j=find(p.first);j<=p.second;j=find(j))mn[j]=i,fa[j]=j+1;
}
}
build(1,0,m+1);
for(int i=1;i<=n;i++){
if(val[i]){
ans+=c.query(val[i]+1);
c.update(val[i]);
update(1,0,m+1,val[i]+1,m+1,1);
}
}
for(int i=1;i<=n;i++){
if(val[i]){
update(1,0,m+1,val[i]+1,m+1,-1);
update(1,0,m+1,0,val[i]-1,1);
}
else{
pair<int,int>p=query(1,0,m+1,mn[i],m+1);
ans+=p.first;
if(p.second)update(1,0,m+1,0,p.second-1,1);
}
}
printf("%lld\n",ans);
}
int main(){
int tim;scanf("%d",&tim);
while(tim--)solve();
return 0;
}
[NOI2022] 二次整数规划问题
你觉得我会写吗?这篇博要是在 cnblogs 超过 20 个踩我就写。
妈妈生的,怎么这么能踩。
看题面发现这个限制非常奇怪。数据范围也很奇怪。然后
先看
一个数什么时候只能取
然后是
但是我们这时候还有第二类约束的限制,决定了一些点只能选
最后是
确定
把所有点
对于矩形和一 / 二 / 四象限有交的情况,只需要上边三个点。对于全部在第三象限的情况,则是整个点集的右上凸包,根据集训队论文它的大小上界是
于是套用最小乘积生成树的做法,只需要求两点间离连线最远的点。设两个点为
#include <iostream>
#include <algorithm>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,k,q,l[610],r[610],cnt[10],val[10],size[610],belong[610];
vector<pair<int,int> >g[610];
int calc(){
int ans=0;
for(int i=2;i<k;i++)ans+=val[i]*cnt[i];
for(int i=1;i<=k;i++){
for(int j=max(i-1,1ll);j<=min(i+1,k);j++)ans+=1000000*cnt[i]*cnt[j];
}
return ans;
}
struct gra{
int v,w,next;
}edge[100010];
int t,head[3010];
void Add(int u,int v,int w){
edge[++t].v=v;edge[t].w=w;edge[t].next=head[u];head[u]=t;
}
void add(int u,int v,int w){
Add(u,v,w);Add(v,u,0);
}
int S,T,dis[3010],head2[3010];
bool bfs(int st){
queue<int>q;
for(int i=0;i<=T;i++)head2[i]=head[i],dis[i]=0;
dis[st]=1;q.push(st);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=edge[i].next){
if(edge[i].w&&!dis[edge[i].v]){
dis[edge[i].v]=dis[x]+1;
if(edge[i].v==T)return true;
q.push(edge[i].v);
}
}
}
return false;
}
int dfs(int x,int flow){
if(x==T)return flow;
int sum=0;
for(int &i=head2[x];i;i=edge[i].next){
if(edge[i].w&&dis[edge[i].v]==dis[x]+1){
int ret=dfs(edge[i].v,min(flow,edge[i].w));
if(ret){
flow-=ret;sum+=ret;
edge[i].w-=ret;edge[i^1].w+=ret;
if(!flow)break;
}
else dis[edge[i].v]=-1;
}
}
return sum;
}
struct node{
int x,y;
node operator-(const node&s)const{
return {x-s.x,y-s.y};
}
int operator^(const node&s)const{
return x*s.y-y*s.x;
}
};
vector<node>tubao;
node dinic(){
while(bfs(S))dfs(S,inf);
node ans={};
for(int i=1;i<=n;i++){
if(size[i]==-1)continue;
if(l[i]>=2&&r[i]<=4){
if(!dis[i])ans.x+=size[i];
else if(dis[i+n])ans.y+=size[i];
}
}
return ans;
}
void solve(node X,node Y){
int val1=Y.x-X.x,val2=X.y-Y.y;t=1;
S=0,T=2*n+1;
for(int i=0;i<=T;i++)head[i]=0;
for(int i=1;i<=n;i++){
if(size[i]==-1)continue;
if(l[i]>=2&&r[i]<=4){
if(l[i]>=3)add(S,i,inf);
else add(S,i,val1*size[i]);
if(r[i]<=3)add(i+n,T,inf);
else add(i+n,T,val2*size[i]);
if(l[i]==4||r[i]==2)add(i,i+n,inf);
else add(i,i+n,(val1+val2)*size[i]);
}
}
for(int x=1;x<=n;x++){
for(pair<int,int>p:g[x]){
int v=p.first,w=p.second;
if(belong[x]!=belong[v]&&size[belong[x]]!=-1&&size[belong[v]]!=-1&&w==1)add(belong[x]+n,belong[v],inf);
}
}
node mid=dinic();
if(((Y-mid)^(X-mid))<0){
tubao.push_back(mid);
solve(X,mid);solve(mid,Y);
}
}
void build(){
for(int i=1;i<=n;i++){
if(size[i]==-1)continue;
size[i]=-1;int cnt=0;
queue<int>q;q.push(i);
while(!q.empty()){
int x=q.front();q.pop();cnt++;
belong[x]=i;
for(pair<int,int>p:g[x]){
int v=p.first,w=p.second;
if(size[v]!=-1&&!w){
size[v]=-1;q.push(v);
}
}
}
size[i]=cnt;
}
tubao.push_back({cnt[2],cnt[4]});
int cnt2=0,cnt4=0;
for(int i=1;i<=n;i++){
if(l[i]==2)cnt2++;
if(r[i]==4)cnt4++;
}
node X={cnt[2],cnt4},Y={cnt2,cnt[4]};
tubao.push_back(X);tubao.push_back(Y);
solve(X,Y);
}
int getans(node x){
cnt[2]=x.x,cnt[4]=x.y,cnt[3]=n-cnt[1]-cnt[2]-cnt[4]-cnt[5];
return calc();
}
signed main(){
int tim;scanf("%lld%lld",&tim,&tim);
while(tim--){
scanf("%lld%lld%lld%lld",&k,&n,&m,&q);
for(int i=1;i<=n;i++)scanf("%lld%lld",&l[i],&r[i]);
for(int i=1;i<=m;i++){
int u,v,w;scanf("%lld%lld%lld",&u,&v,&w);
g[u].push_back(make_pair(v,w));g[v].push_back(make_pair(u,w));
}
for(int i=1;i<=n;i++){
for(int x=1;x<=n;x++){
for(pair<int,int>p:g[x]){
int v=p.first,w=p.second;
l[v]=max(l[v],l[x]-w);
r[v]=min(r[v],r[x]+w);
}
}
}
for(int i=1;i<=n;i++){
if(r[i]!=1&&l[i]!=k)l[i]=max(l[i],2ll),r[i]=min(r[i],k-1);
if(l[i]==r[i])cnt[l[i]]++;
else cnt[0]++;
}
if(k==5)build();
while(q--){
for(int i=2;i<k;i++)scanf("%lld",&val[i]);
if(k==3)printf("%lld\n",calc());
if(k==4){
int ans=0;
cnt[2]+=cnt[0];ans=max(ans,calc());cnt[2]-=cnt[0];
cnt[3]+=cnt[0];ans=max(ans,calc());cnt[3]-=cnt[0];
printf("%lld\n",ans);
}
if(k==5){
int ans=0;
for(node x:tubao)ans=max(ans,getans(x));
printf("%lld\n",ans);
}
}
for(int i=1;i<=n;i++)g[i].clear(),size[i]=0;
for(int i=0;i<=k;i++)cnt[i]=0;
tubao.clear();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)