2022NOIPA层联测7 找 女 朋 友
T2【暴力:BFS/set骗分】给出一棵树,初始全部是白点,每次操作【1】把pos节点颜色取反【2】查询距离pos节点距离最近的黑点。(n,q<=1e5)
40分暴力
发现每个点作为询问点,能够对他产生贡献的节点来自于子树和向上每一级树根,于是开一个set维护每个节点的所有决策(子树内距离最近的节点),跳父亲就行。(O(q*dep(tree)))
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1e5+100;
set<pair<int,int> >s[N];
set<pair<int,int> >::iterator it1;
set<int>ey;
set<int>::iterator it2;
int head[N],fa[N],tot,n,q,cot1;
bool cor[N];
struct Node
{
int to,nxt;
}e[N<<1];
struct NODE
{
int opt,x;
}que[N];
inline void Add(int x,int y)
{
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
}
inline void Dfs(int x,int ff)
{
fa[x]=ff;
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==ff)continue;
Dfs(to,x);
}
}
inline void Ins(int x)
{
++cot1;
pair<int,int>tmp=make_pair(0,x);//距离,编号
s[x].insert(tmp);
//chu("insert:(%d %d)\n",tmp.first,tmp.second);
while(fa[x])
{
x=fa[x];
tmp.first++;
s[x].insert(tmp);
//chu("from(%d):insert:(%d %d)\n",x,tmp.first,tmp.second);
}
}
inline void Clear(int x)
{
--cot1;
pair<int,int>tmp=make_pair(0,x);
s[x].erase(tmp);
while(fa[x])
{
x=fa[x];
tmp.first++;
s[x].erase(tmp);
}
}
inline int Query(int x)
{
pair<int,int>tmp=make_pair(1e9,0);//不可能的值
pair<int,int>go;
if(!s[x].empty())
{
tmp=min(tmp,*s[x].begin());
}
int ds=0;
while(fa[x])
{
x=fa[x];
++ds;
if(s[x].empty())continue;
go=(*s[x].begin());
go.first+=ds;
tmp=min(go,tmp);
}
return tmp.first;
}
int main()
{
// freopen("b1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();q=re();
_f(i,1,n-1)
{
int u=re(),v=re();
Add(u,v);Add(v,u);
}
Dfs(1,0);
int nxt=0;
_f(i,1,q)que[i].opt=re(),que[i].x=re();
for(rint i=0;i<=q;)
{
nxt=i+1;//找到下一个询问(默认0是询问)
while(que[nxt].opt==1)++nxt;
if(nxt>q)break;//没有询问了
//i+1~nxt-1都是修改
if(ey.size())ey.clear();//有效操作
_f(j,i+1,nxt-1)
{
int ele=que[j].x;
if(ey.find(ele)!=ey.end())ey.erase(ele);
else ey.insert(ele);
}
for(it2=ey.begin();it2!=ey.end();++it2)
{
cor[*it2]^=1;
if(cor[*it2]==0)Clear((*it2));
else Ins((*it2));//加入
}
if(cot1)chu("%d\n",Query(que[nxt].x));//xunwen,要存编号!因为问的是这个
else chu("-1\n");
i=nxt;
}
return 0;
}
/*
5 3
1 2
2 3
3 4
3 5
2 1
1 2
2 1
暴力先出来?
每个节点一个
multiset<>存出现的编号
(n^2空间会死的(每次一条log连,1e5*20,还可以哈))
对于每次询问:
ans来自于:
【1】自己的set【2】向上的祖先的dis+祖先的set(min)
直接跳父亲
对于每次修改:
对自己要么删除自己,要么加入自己(0)
对于祖先,
insert/delete:dis(只需要)
O(q*dep*log)
O(q*m*log)
d
*/
90分暴力
如果数据和询问随机生成,那么很大概率黑点和询问点均匀分布,所以在修改只进行标记,在询问BFS第一次找到的黑点就是最优答案。
100分
支持删除的永久化标记的线段树:用set维护永久化标记(其实也是一个惯常的套路了)
发现对于每个点的答案可以表示成\(dep[u]+dep[key]-2*dep[lca(u,key)]\),u是变化的,但是可以dfn序上维护\(dep[key]-2*dep[lca(u,key)]\),因为对于每个黑点可以影响的只有祖先,所以维护每个点·作为lca的贡献,minn代表\(-2*dep[lca]\)区间·最小,ans=minn+dep[u],u是黑点,用set的tag标识累加,然后删除就直接erase,ans的更新用剩下集合的tag或者儿子(empty!)。
注意ans和minn的最优是独立的,我要选出所有的最小的minn组合最小的tag然后成为ans,所以query需要上传一个pair,临时合并子树
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1e5+100;
int n,Q,c[N];
int head[N],tot;
int fa[N],siz[N],mson[N],top[N],rk[N],dfn[N],tim,dep[N];
struct Node
{
int to,nxt;
}e[N<<1];
inline void Add(int x,int y)
{
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
}
int chc=0;
inline void dfs(int x,int ff)
{
siz[x]=1;fa[x]=ff;dep[x]=dep[ff]+1;
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==ff)continue;
// if(to==35||x==35)chc=1;
// if(x==181||x==93||to==12)
//if(chc)chu("%d-->%d\n",x,to);
dfs(to,x);
siz[x]+=siz[to];
if(siz[to]>siz[mson[x]])mson[x]=to;
}
// if(x==35)chc=0;
}
inline void slpf(int x,int belong)
{
top[x]=belong;
dfn[x]=++tim;rk[tim]=x;
if(!mson[x])return;
slpf(mson[x],belong);
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(top[to])continue;
slpf(to,to);
}
}
struct Segment_Tree
{
int minn[N<<2],ans[N<<2];
multiset<int>tag[N<<2];
#define lson (rt<<1)
#define rson (rt<<1|1)
inline void Build(int rt,int l,int r)
{
ans[rt]=1e9;
if(l==r){
minn[rt]=-2*dep[rk[l]];
//if(l==103)chu("node:%d\n",rt);
return;}
int mid=(l+r)>>1;
Build(lson,l,mid);Build(rson,mid+1,r);
minn[rt]=min(minn[lson],minn[rson]);
}
inline void Pushup(int rt)
{
ans[rt]=min(ans[lson],ans[rson]);
if(!tag[rt].empty())ans[rt]=min(ans[rt],minn[rt]+(*tag[rt].begin()));//??
//chu("ansnow[%d]:%d\n",rt,ans[rt]);
}
inline void Change(int rt,int l,int r,int L,int R,int val,int tp)
{
if(L<=l&&r<=R)
{
if(tp==1)//要加入
{
tag[rt].insert(val);
ans[rt]=min(ans[rt],minn[rt]+val);
// chu("ans[%d]:%d val:%d\n",rt,ans[rt],val);
}
else
{
//删除
tag[rt].erase(tag[rt].find(val));
if(l!=r)ans[rt]=min(ans[lson],ans[rson]);
else ans[rt]=1e9;
if(!tag[rt].empty())ans[rt]=min(ans[rt],minn[rt]+(*tag[rt].begin()));
}
return;
}
int mid=(l+r)>>1;
if(L<=mid)Change(lson,l,mid,L,R,val,tp);
if(R>mid)Change(rson,mid+1,r,L,R,val,tp);
Pushup(rt);
return ;
}
inline pair<int,int> Query(int rt,int l,int r,int L,int R)
{
// chu("(rt:%d)(%d %d)(%d %d):%d %d\n",rt,l,r,L,R,minn[rt],ans[rt]);
if(L<=l&&r<=R)return make_pair(minn[rt],ans[rt]);
int mid=(l+r)>>1;
pair<int,int>ch1,ch2;
//chu("now %d %d:que:%d %d\n",l,r,L,R);
if(R<=mid)ch1=Query(lson,l,mid,L,R);
else if(L>mid)ch1=Query(rson,mid+1,r,L,R);
else
{
ch1=Query(lson,l,mid,L,R);
ch2=Query(rson,mid+1,r,L,R);
ch1.first=min(ch1.first,ch2.first);
ch1.second=min(ch1.second,ch2.second);
}//需要push?不需要
// chu("return (%d):(%d %d)\n",rt,ch1.first,ch1.second);
if(!tag[rt].empty())ch1.second=min(ch1.second,ch1.first+(*tag[rt].begin()));
return ch1;
}
}T;
inline void Change(int x)
{
int kp=x;
if(c[x]==1)
{
c[x]=0;
while(x)
{
T.Change(1,1,n,dfn[top[x]],dfn[x],dep[kp],0);
x=fa[top[x]];
}
}
else
{
c[x]=1;
while(x)
{
T.Change(1,1,n,dfn[top[x]],dfn[x],dep[kp],1);
x=fa[top[x]];
}
}
}
inline int Query(int x)
{
int ans=dep[x],now=1e9;
// chu("tag:%d\n",*T.tag[392].begin());
while(x)
{
// chu("query:%d--%d\n",dfn[top[x]],dfn[x]);
now=min(now,T.Query(1,1,n,dfn[top[x]],dfn[x]).second);
x=fa[top[x]];
}
// chu("now:%d\n",now);
if(now>(n<<1))return -1;
else return now+ans;
}
int main()
{
// freopen("b1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),Q=re();
_f(i,1,n-1)
{
int x=re(),y=re();
Add(x,y);Add(y,x);
}
//chu("out\n");
dfs(1,0);
slpf(1,1);
//chu("%d %d\n",dep[181],dep[93]);
//_f(i,1,n)chu("%d ",dfn[i]);chu("\n");
// chu("dfn[%d]:%d\n",4,dfn[4]);
T.Build(1,1,n);
_f(i,1,Q)
{
int opt=re();int x=re();
if(opt==1)Change(x);
else chu("%d\n",Query(x));
}
return 0;
}
/*
5 3
1 2
2 3
3 4
3 5
2 1
1 2
2 1
*/
T3【暴力:DFS】给出二分图,求边权&和=0......127的选边完美匹配方案是否存在。(n<=100)
50分暴力
考虑最直接的枚举,如果对于每个点都枚举边,生成最终的二分图再求权值和,O(n!)级别的难以接受。发现对于边&的限制的严格性,对于1的个数比较多的,绝大部分的决策在一开始就应该被舍弃,对于1比较少的,很容易找到方案,所以直接枚举最终目标的&和值,然后dfs找目标,一旦不合法就立刻回溯减少成本,效果理想。
虽然看起来多一个100的循环,但是....作为暴力真的蛮不错了
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=-1;
int n,m;
int head[110],tot;
struct Node
{
int to,nxt,w;
}e[110*110];
int vc[140],lg[140],vis[140],mach[140],bao[140];//vc[x]=y:边权是x的边有y条,lg[x]=y:^x>=x的边权有多少;
inline void Add(int x,int y,int z)
{
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
e[tot].w=z;
}
inline bool Match(int x,int goal,int now)//当前在给谁找匹配,目标边权,已达到边权
{
//goal & now>=goal
if((now)<goal)return 0;
if(x==n+1)
{
if(goal==now)return 1;
}
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(!mach[to]&&(e[i].w&goal)>=goal)
{
mach[to]=1;
if(Match(x+1,goal,now&(e[i].w)))
{
return 1;
}
mach[to]=0;
}
}
return 0;
}
int main()
{
//freopen("2.in","r",stdin);
//freopen("1.out","w",stdout);
n=re(),m=re();
_f(i,1,m)
{
int x=re(),y=re(),z=re();
bao[y]=1;
Add(x,y,z);//只对左部匹配
++vc[z];
}
_f(i,1,n)
if(!bao[i])
{
_f(j,0,127)chu("0");return 0;
}
_f(i,0,127)
_f(j,i,127)
if((j&i)>=i)lg[i]+=vc[j];
_f(i,0,127)
{
if(lg[i]<n)
{
chu("0");continue;
}
fill(mach+1,mach+1+n,0);
if(Match(1,i,127))chu("1");
else chu("0");
}
return 0;
}
/*
1 1
1 1 1
3 6
1 1 2
1 2 1
2 2 6
2 3 5
3 1 3
3 3 4
make数据出BUG了!!!!!
*/
T4【点分治优化的DP】给出一棵树,每个节点有(a,b)关键值,求选出一个联通块,使得sigma(ai)<=m,bi最大。(n<=1000,m<=10000)
70tps暴力
每次选进来一个点到决策集合,set维护,aibi同时单调,否则舍弃。
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1000+100;
int tot,n,head[N],m,a[N],b[N];
struct AK
{
int ar,br;
bool operator<(const AK&U)const
{
return br<U.br;//br内部保证
}
};
set<AK>s[N],tmp,as;
set<AK>::iterator it;
vector<AK>bag;
struct Nof
{
int to,nxt;
}e[N<<1];
inline void Add(int x,int y)
{
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
}
int ans;
inline void Dfs(int x,int ff)
{
//处理子树信息
//chu("now deal:%d %d\n",x,ff);
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==ff)continue;
//chu("%d(%d,%d) to :%d(%d,%d)\n",x,a[x],b[x],to,a[to],b[to]);
Dfs(to,x);
}
s[x].insert((AK){a[x],b[x]});
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==ff)continue;
as.clear();//放置我新加入的决策点,但是新加入的怎么转移?
for(AK rp:s[x])as.insert(rp);
for(AK s1:s[x])
for(AK s2:s[to])
{
if(s1.ar+s2.ar>m)break;//小根堆?
AK tp=(AK){s1.ar+s2.ar,s1.br+s2.br};
it=as.lower_bound(tp);
if(it==as.end()||tp.ar<(*it).ar)//如果它删不掉
{
if(it!=as.begin())//只有一个>=它的,没得删
{
--it;
while(1)
{
if((*it).ar>=tp.ar)
{
// bag.push_back((*it));//在ar里面删除它
as.erase(it);
}
else break;
if(it==as.begin())break;
--it;
}
}
as.insert(tp);
}
}
swap(s[x],as);
// s[x].clear();
// for(AK rp:as)s[x].insert(rp);
}
//chu("from:dot:%d the ans is:%d\n",x,(*s[x].rbegin()).br);
ans=max(ans,(*s[x].rbegin()).br);
// chu("nowans:%d\n",ans);
}
int main()
{
//freopen("d1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),m=re();
_f(i,1,n)
{
a[i]=re(),b[i]=re();
}
_f(i,1,n-1)
{
int u=re(),v=re();
Add(u,v);Add(v,u);
}
Dfs(1,0);//dfs
chu("%d",ans);
return 0;
}
/*
*/
100tps正解
\(dp[i][j]表示以i为根,a的和值是j的b最大值\),如果dfs进行子树合并上溯,\(O(n*m^2)\),发现答案对于一个节点只来自2部分,以x为根的块和不包含x的块。点分治:
以每个点rt为根,此时的dp表示强制包含rt节点的bi最大值,在进行转移的时候进行2次,
【1】第一次把rt的信息传递下去,\(dp[son][j+a[son]]=dp[father][j]+b[son]\),因为son选爸爸必须选
【2】第二次把儿子的合并回去,表示以rt为根的所有联通情况都聚集了。\(dp[x][j]=max(dp[x][j],dp[son][j])\)
\(O(n*log(n))\)
关于点分治模板的坑:【1】每次选择子树大儿子最小的,root和sum和mson[0]都要update
【2】vis只在solve里面update
点击查看代码
//慎独,深思,毋躁,自律,专注,勿生妄念,极致,不念过往,不放当下
#include<bits/stdc++.h>
using namespace std;
#define chu printf
#define _f(i,a,b) for(register int i=(a);i<=(b);++i)
#define f_(i,a,b) for(register int i=(a);i>=(b);--i)
#define inf 2147483647
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1000+100,M=10000+100;
int n,m,head[N],tot,a[N],b[N],mson[N],root,siz[N],sum,vis[N];ll ans;
ll dp[N][M];//表示在i节点,a是j的最大b
struct node
{
int to,nxt;
}e[N<<1];
inline void Add(int x,int y)
{
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
}
inline void Findrt(int x,int ff)
{
siz[x]=1;mson[x]=0;
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==ff||vis[to])continue;
Findrt(to,x);
siz[x]+=siz[to];
mson[x]=max(mson[x],siz[to]);
}
mson[x]=max(mson[x],sum-siz[x]);
if(mson[x]<mson[root])root=x;
}
//dp[i][j]:到达i节点ai的和值是j的b的最大值
inline void Dfs(int x,int ff)
{
_f(i,0,m)dp[x][i]=-1e16;
_f(i,a[x],m)dp[x][i]=dp[ff][i-a[x]]+b[x];//强制算上子节点的贡献
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==ff||vis[to])continue;
Dfs(to,x);
_f(j,0,m)dp[x][j]=max(dp[x][j],dp[to][j]);
}
}
inline void Solve(int x)
{
// memset(dp,0,sizeof(dp));
Dfs(x,0);//解决x为根的子树贡献
// chu("now solve:%d\n",x);
_f(i,0,m)ans=max(dp[x][i],ans);
vis[x]=1;
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(vis[to])continue;
sum=siz[to];
mson[root=0]=M;
Findrt(to,0);
Solve(root);
}
}
int main()
{
// freopen("d1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),m=re();
_f(i,1,n)a[i]=re(),b[i]=re();
_f(i,1,n-1)
{
int u=re(),v=re();
Add(u,v);Add(v,u);
}
sum=n;root=0;mson[root]=M;
Findrt(1,0);
//chu("root:%d\n",root);
Solve(root);
chu("%lld",ans);
return 0;
}
/*
3 2
1 1
2 3
1 1
1 2
1 3
*/
类似题目:HDU6643,求乘积不超过m的权值最大。
巧妙在于对状态压缩,第二个维度的乘积被变成了根号下个数.对于m/S的S,结果一样都是等效的。
\(dp[i][j]:代表i节点,还最多容纳乘积是j号元素的最大值\)
\(w[i]表示m/S=i的会出现的i的个数,f[i]是离散化后的值\)
\(if(a[u]<=f[i])dp[u][w[f[i]/a[u]]=max(dp[father][i])\)
合并就一样了