NOIP模拟100(多校32)

T1 饥饿的狐狸

解题思路

贪心签到题。

最小值的做法就是对于温度比水小的从大到小吃,然后喝一口水,然后把剩下的从小到大吃掉。

最大值的做法,几乎就是大的挑一个小的挑一个间隔着吃,可以排完序之后双指针扫两边,但是我码了一大堆。。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+10;
int n,ans1,ans2,val,las,s[N];
priority_queue<int> q1;
priority_queue<int,vector<int>,greater<int> > q2;
void solve1()
{
	for(int i=1;i<=n;i++)
		if(s[i]>=val) q1.push(s[i]);
		else q2.push(s[i]);
	las=val;
	while(!q1.empty()&&!q2.empty())
	{
		int x=q1.top(),y=q2.top(); q1.pop(); q2.pop();
		ans1+=x-las+x-y; las=y;
	}
	if(q1.empty()&&q2.empty()) return ;
	if(!q1.empty())
	{
		multiset<int> res;
		ans1+=val-las;
		while(!q1.empty()) res.insert(q1.top()),q1.pop();
		while(res.size()>=2)
		{
			int x=*res.begin(),y=*res.rbegin(); if(y-x<=x-val) break;
			res.erase(res.find(x)); res.erase(res.find(y));
			ans1+=y-val+y-x;
		}
		while(res.size()) ans1+=(*res.begin())-val,res.erase(res.begin());
		return ;
	}
	multiset<int> res;
	while(!q2.empty()) res.insert(q2.top()),q2.pop();
	while(res.size()>=2)
	{
		int x=*res.begin(),y=*res.rbegin(); if(y-x<=val-y) break;
		res.erase(res.find(x)); res.erase(res.find(y));
		ans1+=val-x+y-x;
	}
	while(res.size()) ans1+=val-(*res.begin()),res.erase(res.begin());
}
void solve2()
{
	for(int i=1;i<=n;i++)
		if(s[i]>=val) q1.push(s[i]);
		else q2.push(s[i]);
	las=val;
	while(!q1.empty()&&!q2.empty())
	{
		int x=q1.top(),y=q2.top(); q1.pop(); q2.pop();
		ans2+=las-y+x-y; las=x;
	}
	if(q1.empty()&&q2.empty()) return ;
	if(!q1.empty())
	{
		multiset<int> res;
		while(!q1.empty()) res.insert(q1.top()),q1.pop();
		while(res.size()>=2)
		{
			int x=*res.begin(),y=*res.rbegin(); if(y-x<=x-val) break;
			res.erase(res.find(x)); res.erase(res.find(y));
			ans2+=y-val+y-x;
		}
		while(res.size()) ans2+=(*res.begin())-val,res.erase(res.begin());
		return ;
	}
	multiset<int> res;
	ans2+=las-val;
	while(!q2.empty()) res.insert(q2.top()),q2.pop();
	while(res.size()>=2)
	{
		int x=*res.begin(),y=*res.rbegin(); if(y-x<=val-y) break;
		res.erase(res.find(x)); res.erase(res.find(y));
		ans2+=val-x+y-x;
	}
	while(res.size()) ans2+=val-(*res.begin()),res.erase(res.begin());
}
#undef int
int main()
{
	#define int long long
	freopen("a.in","r",stdin); freopen("a.out","w",stdout);
	n=read(); las=val=read();
	for(int i=1;i<=n;i++) s[i]=read();
	for(int i=1;i<=n;i++)
		if(s[i]<val) q1.push(s[i]);
		else if(s[i]>val) q2.push(s[i]);
	while(!q1.empty()) ans1+=las-q1.top(),las=q1.top(),q1.pop();
	las=val; while(!q2.empty()) ans1+=q2.top()-las,las=q2.top(),q2.pop();
	printf("%lld ",ans1); ans1=0; solve1(); solve2();
	printf("%lld",max(ans1,ans2));
	return 0;
}

T2 保险箱

解题思路

假设密码数组是 \(c\)

那么就要满足 \(c\) 数组的所有值以及 \(n\) 配上任意的系数相加之后都不可以等于 \(1\sim K-1\) 中的任何一个 \(m_i\)

那么根据裴蜀定理可以得到\(\gcd(n,c_1,c_2...)\nmid m_i\)

那么一个比较暴力的做法就出来了:枚举 \(\gcd(n,m_K)\) 的每一个因数判断是否合法,然后选取最大的。

考虑优化一下,对于任何一个 \(m_i\) 而言,它的任何一个因子都是不可以的,那么它在 \(\gcd(n,m_K)\) 的因子这个范围中的因子一定是 \(\gcd(m_i,\gcd(n,m_K))\) 的因数。

于是我们可以先算出来 \(\gcd(n,m_K)\) 的每一个质因子,然后对于每一个 \(\gcd(m_i,\gcd(n,m_K))\) 记忆话搜索就好了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=25e4;
int n,g,temp,m,mx,s[N];
vector<int> pri;
set<int> res,in;
inline int gcd(int x,int y){return !y?x:gcd(y,x%y);}
bool check(int x){return res.find(x)==res.end();}
void dfs(int x){if(!check(x))return ;res.insert(x);for(auto y:pri)if(x%y==0)dfs(x/y);}
#undef int
int main()
{
	#define int register long long
	freopen("b.in","r",stdin); freopen("b.out","w",stdout);
	n=read(); m=read();
	for(int i=1;i<=m;i++) s[i]=read(); temp=g=gcd(s[m],n);
	for(int i=1;i<m;i++) in.insert(gcd(g,s[i]));
	for(int i=2;i*i<=temp;i++) if(temp%i==0){pri.push_back(i);while(temp%i==0)temp/=i;}
	if(temp!=1) pri.push_back(temp); for(auto it:in) dfs(it);
	for(int i=mx=1;i*i<=g;i++,mx++) if(g%i==0&&check(i)) printf("%lld\n",n/i),exit(0);
	for(int i=mx-1;i>=1;i--) if(g%(g/i)==0&&check((g/i))) printf("%lld\n",n/(g/i)),exit(0);
	return 0;
}

T3 追逐

解题思路

原题,然而当时是水过去的。

一个点的贡献就是它所有和它相邻的节点的权值和减去经过它之前经过节点的权值,直接暴力枚举起点统计可以获得 70pts 。

考虑树形 DP 。 设 \(f_{i,j}\) 表示 \(i\) 节点从他的子树到他丢下 \(i\) 个磁铁的最大值,\(g_{i,j}\) 表示 \(i\) 节点从他到他的子树丢下 \(i\) 个磁铁的最大值。

转移 DP 数组直接考虑是否在这个节点放磁铁就好了,对于答案的统计直接将两个数组拼起来就好了。

由于路径的正反答案是不相同的,所以需要把连边倒过来在计算一遍。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+10,M=110;
int n,m,ans,all,s[N],cnt[N],f[N][M],g[N][M];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
vector<int> v[N];
void dfs(int x,int fa)
{
	for(int i=1;i<=m;i++) f[x][i]=cnt[x],g[x][i]=cnt[x]-s[fa];
	for(auto to:v[x])
	{
		if(to==fa) continue; dfs(to,x);
		for(int i=0;i<=m;i++) ans=max(ans,f[x][i]+g[to][m-i]);
		for(int i=1;i<=m;i++)
			f[x][i]=max(f[x][i],max(f[to][i],f[to][i-1]+cnt[x]-s[to])),
			g[x][i]=max(g[x][i],max(g[to][i],g[to][i-1]+cnt[x]-s[fa]));
	}
	reverse(v[x].begin(),v[x].end());
	for(int i=1;i<=m;i++) f[x][i]=cnt[x],g[x][i]=cnt[x]-s[fa];
	for(auto to:v[x])
	{
		if(to==fa) continue;
		for(int i=0;i<=m;i++) ans=max(ans,f[x][i]+g[to][m-i]);
		for(int i=1;i<=m;i++)
			f[x][i]=max(f[x][i],max(f[to][i],f[to][i-1]+cnt[x]-s[to])),
			g[x][i]=max(g[x][i],max(g[to][i],g[to][i-1]+cnt[x]-s[fa]));
	}
}
#undef int
int main()
{
	#define int register long long
	freopen("c.in","r",stdin); freopen("c.out","w",stdout);
	n=read(); m=read();
	for(int i=1;i<=n;i++) s[i]=read();
	for(int i=1,x,y;i<n;i++)
		x=read(),y=read(),
        v[x].push_back(y),v[y].push_back(x);
	for(int i=1;i<=n;i++) for(auto to:v[i]) cnt[i]+=s[to];
    dfs(1,0); printf("%lld",ans);
	return 0;
}

T4 字符串

解题思路

最妙的就是关于题意的转化了。

code

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=5e5+10;
int n,q,t;
char ch[N];
struct Node{int len,l,r,sum;};
Node const operator + (Node x,Node y)
{
	Node z; z.sum=x.sum+y.sum;
	z.len=max(x.r+y.l,max(x.len,y.len));
	z.l=max(x.l,y.l+x.sum); z.r=max(y.r,x.r+y.sum);
	return z;
} 
struct Segment_Tree
{
	Node tre[N<<2];
	#define push_up(x) tre[x]=tre[ls]+tre[rs]
	void build(int x,int l,int r)
	{
		if(l==r) return t=ch[l]=='C'?1:-1,tre[x]=(Node){t,t,t,t},void();
		int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r); push_up(x);
	}
	Node query(int x,int l,int r,int L,int R)
	{
		if(L<=l&&r<=R) return tre[x]; int mid=(l+r)>>1;
		if(L<=mid&&R>mid) return query(ls,l,mid,L,R)+query(rs,mid+1,r,L,R);
		if(L<=mid) return query(ls,l,mid,L,R); return query(rs,mid+1,r,L,R);
	}
}T;
#undef int
int main()
{
	#define int register long long
	freopen("d.in","r",stdin); freopen("d.out","w",stdout);
	n=read(); scanf("%s",ch+1); T.build(1,1,n); q=read();
	while(q--)
	{
		int l,r; l=read(); r=read(); Node temp=T.query(1,1,n,l,r);
		printf("%lld\n",max(0ll,max(temp.len,max(temp.l,temp.r)))-temp.sum);
	}
	return 0;
}
posted @ 2021-11-17 17:37  Varuxn  阅读(69)  评论(0编辑  收藏  举报