CPS-S模拟3:score and rank;HZOI大作战;Delov的旅行; gtm和joke的星球

CPS-S模拟3:score and rank;HZOI大作战;HZOI大作战;HZOI大作战

T1:贪心+单调队列维护

T2:树上倍增

T3:归并排序优化DP

T4:斯坦那树

T1:题目大意:给你长度n的序列,求最少删掉多少的数字,可以使得序列的任意连续子序列的区间和值<S(S可以是负数)n<=1e6

贪心+优先队列:
(1)首先如果任意ai>=0,贪心思路很好想,就是从小到大把序列排序,然后从最大的数开始删除,直到整个序列sum<S
(2)你序列的数蹦来蹦去不好弄,我就强制从一端开始构造,保证每个时刻的子序列sum<=S,就可以。
维护一个大根堆,维护当前考虑最劣情况的前缀和值sum,每加进来一个数,
如果>0,sum+=val,这时候如果sum>S,那么就从堆里删除数(从最大的开始),直到合法。
如果<0,考虑当加入这个数,如果sum变成负数,那么前面的一定会合法,可以直接忽略不计,直接保证后面合法就行。
如果sum还是正数,就要把前面的区间和后面sum连续起来,在sum'里把这个负数消掉 ,贪心用小的去消(大的留着抵消),然后把消完的数直接等效成一个数扔进set里面 。(考虑删掉这个差值的意义
:把最后一个用来抵消的正数删掉,此时区间减少的值就是这个差值)
积累函数用法:s.rbegin():末尾位置
s.erase(prev(s.end()))删除最后一个元素



#include<bits/stdc++.h>
using namespace std;
#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 chu printf
#define ll long long
#define ull unsigned long long
#define rint register int
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=131072+100;
int n;
multiset<ll>p;
ll S;
int main()
{
    freopen("score.in","r",stdin);
    freopen("score.out","w",stdout);
	n=re();S=re();
	if(S<=0)
	{
		int cnt=0;
		_f(i,1,n)
		{
			ll val=re();
			if(val>=S)++cnt;
		}
		chu("%d",cnt);
		return 0;
	}
	ll sum=0;int cnt=0;
	_f(i,1,n)
	{
		ll val=re();
		if(val<0)
		{
			while(!p.empty()&&val<0)
			{
				val+=*p.begin();
				sum-=*p.begin();
				p.erase(p.begin());
			}
			if(val>0)p.insert(val),sum+=val;
		}
		else
		{
			sum+=val;p.insert(val);
			while(!p.empty()&&sum>=S)
			{
				sum-=*p.rbegin();
				p.erase(prev(p.end()));
				++cnt;
			}
		}
	}
	chu("%d",cnt);
    return 0;
}

部分分:状态压缩枚举,但是注意状态表示的最小不一定是决策的最小,不能直接跳

T3:https://tg.hszxoj.com/contest/524/problem/3

部分分:
5分:一定是跨深度最大的一条路线是Max,直接算出来。
15分:感觉是正解...... 但是它其实NF地忽略了一些情况:定义dp[i][0/1]:到i节点为根的子树,某个叶子节点到i的最小距离:
转移,就考虑维护dp和答案(答案会产生在任意2个叶子合并的时候)
dp[x][0]=min(dp[lson][0/1]+val[x][lson])(右边一样)
ans=max(ans,dp[x][0]+dp[x][1])
含义:考虑一条叶子到lson的路径最短那么我们把它当成回来的二次经过路线(另一条rson不是最短,就当成是第一次遍历到那不花钱),是最优的。
局限:“另一条rson不是最短,就当成是第一次遍历到那不花钱”如果每个节点都这么忽略了一些东西,但是只有1条链上的可以忽略,剩下就不行了。
dp[x][a][b]表示以x为根,x到第一次到达的叶子的距离是a,最后一次到达叶子的距离是b是否可行,这样定义是方便二分。
二分最长距离mid,然后验证是否合法。
正解
dp[x][a][b]=dp[lson][a-dis(x,lson)]&&dp[rson][b-dis(x,rson)]
考虑保留(a,b)一定可能有贡献部分。(a,b)递增递减单调队列思想存储排除一定不合法。
在dfs到rt,考虑先进lson,然后从rson出,这个有mid,考虑对于“出lson,进rson”限制,所以lson_bi确定,rson_ai范围也确定了。
从合法范围中选择bi最小一定最优,(ai合法就行,bi越小,另一个ai就可能越大)。然后真正存进去的是“进lson,出rson”的DP表示2个维度。
没有合法,就是set里面没有状态可以放进去
//Ctrl+Z Ctrl+Y







#include<bits/stdc++.h>
using namespace std;
#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 chu printf
#define ll long long
#define ull unsigned long long
#define rint register int
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=131072*2+100;
//int head[N],tot,n;
//struct Node
//{
//	int to,nxt,w;
//}e[N<<1];
//inline void Add(int x,int y,int w)
//{
//	e[++tot].to=y;e[tot].nxt=head[x];head[x]=tot;e[tot].w=w;
//}
int n,val[N];
struct DP
{
	ll a,b;
	DP(){}
	DP(ll ax,ll bx)
	{
		a=ax;b=bx;
	}
	inline bool operator<(const DP&A)const
	{
		return (a==A.a)?(b<A.b):(a<A.a);
	}
};
set<DP>s[N];
DP ls[N];int cnt;
inline bool dfs(int x,ll mid)
{
	
	int lson=x<<1,rson=(x<<1)|1;
	ll vl=val[lson],vr=val[rson];
	s[x].clear();
	if(lson>n)
	{
		s[x].insert(DP(0,0));return 1;
	}
	if(!dfs(lson,mid))return 0;
	if(!dfs(rson,mid))return 0;
	cnt=0;
	auto it=s[rson].begin(),en=--s[rson].end();
	for(DP rp:s[lson])
	{
		while(it!=en&&(*(next(it))).a+rp.b+vl+vr<=mid)++it;
		if((*it).a+rp.b+vl+vr<=mid)ls[++cnt]=DP(rp.a+vl,(*it).b+vr);
	}
	it=s[lson].begin(),en=--s[lson].end();
	for(DP rp:s[rson])
	{
		while(it!=en&&(*(next(it))).a+rp.b+vl+vr<=mid)++it;
		if((*it).a+rp.b+vl+vr<=mid)ls[++cnt]=DP(rp.a+vr,(*it).b+vl);
	}
	if(!cnt)return 0;
	sort(ls+1,ls+1+cnt);
	int las=1;
	s[x].insert(ls[1]);
	for(int i=2;i<=cnt;++i)
	{
		if(ls[i].a>ls[las].a&&ls[i].b<ls[las].b)s[x].insert(ls[i]),las=i;
	} 
	if(s[x].empty())return 0;
	return 1;
}
int main()
{
    freopen("trip.in","r",stdin);
    freopen("trip.out","w",stdout);
	n=re();
	_f(i,2,n)
	{
		int ai=re(),vi=re();val[i]=vi;//i就是父亲边 
	}
	int dep=log2(n+1)-1;
	ll l=0,r=(ll)dep*2*131072LL+100LL;//最大深度 
	ll ans=0;
	while(l<=r)
	{
		ll mid=(l+r)>>1;
		if(dfs(1,mid))r=mid-1,ans=mid;
		else l=mid+1;
	}
	chu("%lld",ans);
    return 0;
}
/*
部分分:
5分:一定是跨深度最大的一条路线是Max,直接算出来。
15分:感觉是正解...... 但是它其实NF地忽略了一些情况:定义dp[i][0/1]:到i节点为根的子树,某个叶子节点到i的最小距离:
转移,就考虑维护dp和答案(答案会产生在任意2个叶子合并的时候)
dp[x][0]=min(dp[lson][0/1]+val[x][lson])(右边一样)
ans=max(ans,dp[x][0]+dp[x][1])
含义:考虑一条叶子到lson的路径最短那么我们把它当成回来的二次经过路线(另一条rson不是最短,就当成是第一次遍历到那不花钱),是最优的。
局限:“另一条rson不是最短,就当成是第一次遍历到那不花钱”如果每个节点都这么忽略了一些东西,但是只有1条链上的可以忽略,剩下就不行了。 
dp[x][a][b]表示以x为根,x到第一次到达的叶子的距离是a,最后一次到达叶子的距离是b是否可行,这样定义是方便二分。
二分最长距离mid,然后验证是否合法。
正解
dp[x][a][b]=dp[lson][a-dis(x,lson)]&&dp[rson][b-dis(x,rson)]
考虑保留(a,b)一定可能有贡献部分。(a,b)递增递减单调队列思想存储排除一定不合法。
在dfs到rt,考虑先进lson,然后从rson出,这个有mid,考虑对于“出lson,进rson”限制,所以lson_bi确定,rson_ai范围也确定了。
从合法范围中选择bi最小一定最优,(ai合法就行,bi越小,另一个ai就可能越大)。然后真正存进去的是“进lson,出rson”的DP表示2个维度。
没有合法,就是set里面没有状态可以放进去
//Ctrl+Z Ctrl+Y
100 Mbps
bit per second
8 MB byte
100*1024*1024字节
100*1924*1024*8位
*/

T4:斯坦纳树板子:对于一个连通图,n个点,选出边使得任意选定K个点连通,求最小边权和。(n<=100,k<=10)

dp[i][j] 表示以i为根已经选的K点集合为j的最小代价。
如果对自己的子树进行合并:
dp[i][j]=min(dp[i][j],dp[i][t]+dp[i][j-t])(t属于j)
【这些状态不重复,因为不同连通块通过i连接,"t"和"j-t"都代表了通过不同连边方式和顺序达到j状态】
如果通过其他子树借边过来:
dp[i][j]=min(dp[i][j],dp[k][j]+dis(i,k))
发现就是最短路,跑一下Dij就行

 	
#include<bits/stdc++.h>
using namespace std;
#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 chu printf
#define ll long long
#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;
struct node
{
	int to,nxt,w;
}e[1010];
int tot,head[110];
int n,m,K,ky[12],dp[110][4200],vis[110];
priority_queue< pair<int,int> >p;
inline void Add(int x,int y,int w)
{
	e[++tot].to=y;e[tot].nxt=head[x];head[x]=tot;e[tot].w=w;
}
inline void Dij(int s)
{
	_f(i,1,n)vis[i]=0;
	while(!p.empty())
	{
		pair<int,int>q=p.top();
		p.pop();
		if(vis[q.second])continue;
		vis[q.second]=1;
		for(int i=head[q.second];i;i=e[i].nxt)
		{
			int to=e[i].to;
			if(dp[to][s]>-q.first+e[i].w)
			{
				dp[to][s]=-q.first+e[i].w;
				p.push(make_pair(-dp[to][s],to));
			}
		}
	}
}
int main()
{
    freopen("steiner.in","r",stdin);
   freopen("steiner.out","w",stdout);
  	n=re(),m=re(),K=re();
  	_f(i,1,m)
  	{
  		int u=re(),v=re(),w=re();
  		Add(u,v,w);Add(v,u,w);
	}
	int mx=(1<<K);
	_f(i,1,n)
	_f(j,0,mx-1)dp[i][j]=1e9;
	_f(i,1,K)
	{
		ky[i]=re();dp[ky[i]][1<<(i-1)]=0;
	}
	_f(s,0,mx-1)
	{
		_f(i,1,n)
		{
			for(int j=s&(s-1);j;j=s&(j-1))
			{
				dp[i][s]=min(dp[i][s],dp[i][j]+dp[i][s-j]);
			}
			if(dp[i][s]<1000000000)p.push(make_pair(-dp[i][s],i));
		}
		Dij(s);
	}
	chu("%d",dp[ky[1]][mx-1]);
    return 0;
}

T5:和T1差不多的在一个数列上维护的操作。洛谷9月赛T3.正解本来是线段树维护区间最小值。但是可以优化成O(n),因为前面一段的可以和后面等效,具体见Catherine_leah的博客https://www.cnblogs.com/Catherine2006/p/16683408.html

可以用公式带入,发现如果前面ai+kv是max,那么ai+kv>ai+1+kv+v,所以一定本一次选ai位置,那么后面差值也满足了

posted on 2022-09-12 18:32  HZOI-曹蓉  阅读(131)  评论(0编辑  收藏  举报