• P1084


    我的炒鸡复杂方法……

    首先考虑二分答案,在 chk 内部可以承受线对的复杂度。

    首先有个很显然的贪心思路:把每个节点尽量往上走(这个只需要树上倍增就可以了),不往其它方向走,这样可以覆盖极多的叶子。但不巧的是这题有个限制,不能跑到根上去。那这时候即使把贪心修补成尽量往上走直到第二层,这个贪心依然失效了一半,因为这样的话每个军队就出不了所在二层子树,但实际上它完全可以跨越根节点去支援其它二层子树。

    没法走到根的军队,显然只有一种选择——尽量往上走了吧,不多说,它能先覆盖掉一些叶子。其它军队,最终一定落在某个二层节点上,因为这样又能覆盖极大叶子集合,走的路又最短。那我们要 chk 的事情就是是否能把这些军队每个分配给某个能到达的二层节点,使得所有覆盖恰好一个没被先覆盖的叶子的二层节点都被至少一个军队分配。

    如果你光让每个军队先走到根节点,再看剩下来的路程能到达哪些二层节点,那把二层节点排个序每个军队能走到的就是个前缀,这个直接贪心傻子都会。关键是每个军队到自己所在的二层节点需要的路程是更少的,这样存在一个特殊点。所以每个军队能到达的二层节点集合其实就是一个前缀,然后有可能带上后面的某个单点。

    贪心感觉不太会贪了。让我们以学术研究的视角重新审视这个问题,就是个二分图完美匹配呗,考虑 Hall 定理。选取一个二层节点集合,看到达它的军队集合大小是否小于它的大小。面对这个特殊图,要做的是筛出来少量的必须要判的集合。注意到如果你选择了 (x)​​,那么暂时不考虑带的单点,你选 (x)​​​ 后面的也不会增加军队,那要求要最苛刻肯定要全选了咯。考虑上带的单点呢?那我们可以枚举选择的最小的 (x)​​,那么 (x)​​ 后面的点对单点触发军队的情况显然是独立的(也就是说每个军队最多被 (1)​ 个二层节点通过单点来触发),那么就可以把所有单点触发军队的二层节点拆开来一个一个考虑。容易发现一旦选了 (x)​ 后面的单点触发至少一个军队的点,一定稳亏不赚,多了 (1)​ 个二层节点,多了 (1+)​ 个军队。所以一定不选。

    所以我们枚举选择的最小二层节点,跟据上述讨论,这次触发的军队集合就是该最小节点触发的集合这么多,不能再多了,就这么定了;然后选择后面所有不会单点触发军队的二层节点。前者数量就是该节点正常触发的数量(就是个后缀嘛)加上单点触发数量(预处理每个二层节点触发的军队数量即可);对后者的维护呢,可以一路 two-pointers 维护当前军队后缀,每删除一个军队就把对应单点打个标记,BIT 维护后缀 (x)​​ 内被打至少一次标记的二层节点数量,减掉就行了。代码写起来细节比较多,较为恶心。

    code
    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define X first
    #define Y second
    #define mp make_pair
    #define pb push_back
    const int inf=0x3f3f3f3f3f3f3f3f;
    int lowbit(int x){return x&-x;}
    const int N=50010,LOG_N=17;
    int n,m;
    vector<pair<int,int> > nei[N];
    vector<int> v;
    int dfn[N],mxdfn[N],nowdfn,mng[N];
    int fa[N][LOG_N],dis[N];
    void dfs(int x=1){
    	mng[dfn[x]=mxdfn[x]=++nowdfn]=x;
    	for(int i=1;i<LOG_N;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
    	for(int i=0;i<nei[x].size();i++){
    		int y=nei[x][i].X,len=nei[x][i].Y;
    		if(y==fa[x][0])continue;
    		fa[y][0]=x;
    		dis[y]=dis[x]+len;
    		dfs(y);
    		mxdfn[x]=mxdfn[y];
    	}
    }
    int up(int x,int w){
    	for(int i=LOG_N-1;~i;i--)if(fa[x][i]>1&&dis[x]-dis[fa[x][i]]<=w)w-=dis[x]-dis[fa[x][i]],x=fa[x][i];
    	return x;
    }
    int d[N];
    bool cmp(int x,int y){return dis[x]<dis[y];}
    struct bitree{
    	int cnt[N];
    	void init(){memset(cnt,0,sizeof(cnt));}
    	void add(int x){
    		while(x<=n)cnt[x]++,x+=lowbit(x);
    	}
    	int Cnt(int x){
    		int res=0;
    		while(x)res+=cnt[x],x-=lowbit(x);
    		return res;
    	}
    	int _cnt(int l,int r){return Cnt(r)-Cnt(l-1);}
    }bit;
    int pos[N];
    int cnt[N];
    bool chk(int w){//remember to make it first
    //	cout<<"-------------------------
    "<<w<<":
    ";
    	memset(d,0,sizeof(d));bit.init();memset(cnt,0,sizeof(cnt));memset(pos,0,sizeof(pos));
    	vector<pair<int,int> > army;
    	vector<int> leaf;
    	for(int i=0;i<v.size();i++){
    		int x=up(v[i],w);
    //		cout<<v[i]<<" up to "<<x<<"!
    ";
    //		cout<<v[i]<<" dis is "<<dis[v[i]]<<"!
    ";
    		if(dis[v[i]]>w)d[dfn[x]]++,d[mxdfn[x]+1]--;
    		else army.pb(mp(w-dis[v[i]],dis[x]<=w-dis[v[i]]?0:x)),cnt[army.back().Y]++;
    	}
    //	for(int i=0;i<army.size();i++)cout<<army[i].X<<" "<<army[i].Y<<"!
    ";
    	for(int i=1;i<=n;i++)d[i]+=d[i-1];
    	for(int i=2;i<=n;i++)if(nei[i].size()==1&&!d[i])leaf.pb(up(mng[i],inf));
    	sort(leaf.begin(),leaf.end(),cmp);leaf.resize(unique(leaf.begin(),leaf.end())-leaf.begin());
    	for(int i=0;i<leaf.size();i++)pos[leaf[i]]=i+1;
    //	for(int i=0;i<leaf.size();i++)cout<<leaf[i]<<" covers leaf
    ";
    	sort(army.begin(),army.end());
    	int now=0;
    	for(int i=0;i<leaf.size();i++){
    		while(now<army.size()&&dis[leaf[i]]>army[now].X){
    			int x=pos[army[now].Y];
    //			cout<<army[now].Y<<" whose pos is "<<x<<"
    ";
    			if(x&&!bit._cnt(x,x))/*cout<<"i fuck @ "<<i<<"
    ",*/bit.add(x);
    			now++;
    		}
    //		cout<<"when "<<i<<" now is "<<now<<"!
    ";
    		if(leaf.size()-i-bit._cnt(i+2,leaf.size()+1)>army.size()-now+cnt[leaf[i]])return false;
    	}
    	return true;
    }
    signed main(){
    	cin>>n;
    	for(int i=1;i<n;i++){
    		int x,y,v;
    		scanf("%lld%lld%lld",&x,&y,&v);
    		nei[x].pb(mp(y,v)),nei[y].pb(mp(x,v));
    	}
    	cin>>m;
    	while(m--){
    		int x;
    		scanf("%lld",&x);
    		v.pb(x);
    	}
    	dfs();
    	int ans=1e16;
    	for(int i=55;~i;i--)if(ans-(1ll<<i)>=0&&chk(ans-(1ll<<i)))ans-=1ll<<i;
    	cout<<(chk(ans)?ans:-1);
    	return 0;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    2016/07/05 配置虚拟域名 三部曲
    sublime下Docblocker插件自定义配置
    掌握Thinkphp3.2.0----标签库
    掌握Thinkphp3.2.0----内置标签
    掌握Thinkphp3.2.0----模版基础
    掌握Thinkphp3.2.0----视图
    掌握Thinkphp3.2.0----自动完成
    掌握Thinkphp3.2.0----自动验证
    掌握Thinkphp3.2.0----CURD
    掌握Thinkphp3.2.0----连贯操作
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-p1084.html
Copyright © 2020-2023  润新知