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])\)
合并就一样了

posted on 2022-10-11 19:35  HZOI-曹蓉  阅读(32)  评论(0编辑  收藏  举报