Educational Codeforces Round 114 (Div. 2)

A.构造

我们有着很简单的构造方法

对于所有的有 n − 1 n-1 n1对匹配括号的序列

我们有两种方案将其变为 n n n对匹配括号的序列

  1. 在外层套一个 ( ) () ()
  2. 在最后添一个 ( ) () ()

可以知道,这两种方案是绝对不可能重复的

而我们又知道 n = 1 n=1 n=1时,有序列 ( ) () ()

因此,好办了

#include<bits/stdc++.h>
using namespace std;
vector<deque<char>> res[55];
int main()
{
	res[1].push_back({'(',')'});
	for (int i=2;i<=50;++i)
	{
		for (int j=0;j<res[i-1].size();++j)
		{
			deque<char> tmp = res[i-1][j];
			tmp.push_back(')');
			tmp.push_front('(');
			res[i].push_back(tmp);
			tmp.pop_back();
			tmp.pop_front();
			tmp.push_back('(');
			tmp.push_back(')');
			res[i].push_back(tmp);
			if (res[i].size()>i)break;
		}
	}
	int t;scanf("%d",&t);
	while (t--)
	{
		int n;scanf("%d",&n);
		for (int i=0;i<n;++i)
		{
			for (char ch:res[n][i])printf("%c",ch);
			puts("");
		}
	}
}

B.思维

如果让我们直接去构造一个有着 m m m对满足条件的索引的序列,那确实是非常困难的

但是这题我们只需判断是否存在

通过画图,我们可以很简单地归纳出一个结论(猜测也是可以的)

序列中满足条件的索引数一定是连续变化的!!

即,如果能组成的序列中最小的满足条件数为 m i n min min,最大为 m a x max max

那么对于 m i n ≤ m ≤ m a x min\le m\le max minmmax m m m一定能够被取到!

问题就为如何计算出 m i n min min m a x max max

m a x max max很好求,颜色相同的放一起就好了 a + b + c − 3 a+b+c-3 a+b+c3

m i n min min的求法为 M A X ( 0 , a − b − c − 1 ) MAX(0,a-b-c-1) MAX(0abc1)

即我们每次都从最大的两种颜色中取出来放进去

#include<bits/stdc++.h>
using namespace std;

int main()
{
	int t;scanf("%d",&t);
	while (t--)
	{
		int a,b,c,m;
		scanf("%d %d %d %d",&a,&b,&c,&m);
		if (a<b)swap(a,b);
		if (a<c)swap(a,c);
		int mn = max(0,a-b-c-1);
		int mx = a+b+c-3;
		if (m<=mx&&m>=mn)printf("YES\n");
		else printf("NO\n");
	}
}

C.思维+推公式

并不是很难

按照题目的含义将公式列出:

假设我们选择了第 i i i个英雄去攻击,所有英雄的强度和为 s u m sum sum

那么我们需要的金币为:

m a x ( 0 , a t t a c k − a [ i ] ) + m a x ( d e f e n s e − s u m + a [ i ] ) max(0,attack-a[i])+max(defense-sum+a[i]) max(0,attacka[i])+max(defensesum+a[i])

我们敏锐的发现, a [ i ] a[i] a[i]似乎可以被消掉

即,如果存在 a [ i ] a[i] a[i],使得 a t t a c k − a [ i ] ≥ 0 attack-a[i]\ge 0 attacka[i]0并且 d e f e c s e − s u m + a [ i ] ≥ 0 defecse-sum+a[i]\ge 0 defecsesum+a[i]0

那么此刻的所需要的金币最小,为 a t t a c k + d e f r c s e − s u m ​ attack+defrcse-sum​ attack+defrcsesum

因此,我们去找是否有 s u m − d e f e c s e ≤ a [ i ] ≤ a t t a c k sum-defecse\le a[i]\le attack sumdefecsea[i]attack

如果没有的话。则代入该区间两边最靠近的值,取小即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 3e18;
const int maxn = 2e5+100;
ll a[maxn];
int n,m;
ll at,df;
ll sum;
inline ll cal(int id)
{
	return max(0LL,at-a[id])+max(0LL,df-sum+a[id]);
}
int main()
{
	scanf("%d",&n);
	sum=0;
	for (int i=1;i<=n;++i)
	{
		scanf("%lld",&a[i]);
		sum+=a[i];
	}
	sort(a+1,a+1+n);
	
	scanf("%d",&m);
	while (m--)
	{
		scanf("%lld %lld",&at,&df);
		ll ans = inf;
		int id = lower_bound(a+1,a+1+n,at)-a;
		if (id<=n&&id>=1)ans = min(ans,cal(id));
		if (id>1)ans = min(ans,cal(id-1));
		id = lower_bound(a+1,a+1+n,sum-df)-a;
		if (id<=n&&id>=1)ans = min(ans,cal(id));
		if (id>1)ans = min(ans,cal(id-1));
		printf("%lld\n",ans);
	}
}

D.搜索剪枝

很明显的一个思路:

我们只需要搜索出前 m + 1 m+1 m+1大的组合,就一定能够找到答案!

好的,大致方针已经定了下来

那么如何搜索呢?

首先,最大的一定是 [ c 1 , c 2 , c 3 , … , c n ] [c_1,c_2,c_3,\dots,c_n] [c1,c2,c3,,cn]

然后,依次我们有搜索方向 [ c 1 − 1 , c 2 , … , c n ] , [ c 1 , c 2 − 1 , … , c n ] , … , [ c 1 , c 2 , … , c n − 1 ] [c_1-1,c_2,\dots,c_n],[c_1,c_2-1,\dots,c_n],\dots,[c_1,c_2,\dots,c_n-1] [c11,c2,,cn],[c1,c21,,cn],,[c1,c2,,cn1]

依靠这种搜索,我们由大到小绝对不会遗漏!

但是关键就是搜索的状态太多了!

考虑到我们只需要搜出前 m + 1 m+1 m+1个即可!

我们可以消减状态数

具体操作

维护一个 s e t set set由大到小,装入每种组合

每次我们拿出最大的组合,然后遍历其所有的搜索方向得到新的组合插入 s e t set set

接下来,如果 s e t set set的大小超过 m + 1 m+1 m+1我们就不断地减去 s e t set set中最小的组合

如此,我们可以完全遍历出前 m + 1 m+1 m+1大的组合

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
set<array<int,10>> se;
int G[15][maxn];
int len[15];
int n,m;
struct node
{
	array<int,10> ar;
	int sum = 0;
	void init()
	{
		for (int i=0;i<10;++i)ar[i]=0;
		sum=0;
	}
	bool operator<(const node& nod)const
	{
		if (sum==nod.sum)return ar>nod.ar;
		return sum>nod.sum;
	}
};
set<node> que;
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
	{
		scanf("%d",&len[i]);
		for (int j=1;j<=len[i];++j)scanf("%d",&G[i][j]);
	}
	scanf("%d",&m);
	for (int i=1;i<=m;++i)
	{
		array<int,10> ar;
		for (int j=0;j<10;++j)ar[j]=0;
		for (int j=0;j<n;++j)scanf("%d",&ar[j]);
		se.insert(ar);
	}
	node cur;
	cur.init();
	for (int i=0;i<n;++i)
	{
		cur.ar[i]=len[i+1];
		cur.sum+=G[i+1][len[i+1]];
	}
	que.insert(cur);
	while (!que.empty())
	{
		cur = *que.begin();
		que.erase(que.begin());
		if (!se.count(cur.ar))
		{
			for (int i=0;i<n;++i)printf("%d ",cur.ar[i]);
			return 0;
		}
		for (int i=0;i<n;++i)if (cur.ar[i]>1)
		{
			node net;net.sum=0;
			for (int j=0;j<10;++j)net.ar[j]=cur.ar[j];
			net.ar[i]--;
			for (int j=0;j<10;++j)net.sum+=G[j+1][net.ar[j]];
			que.insert(net);
			while (que.size()>m+1)que.erase(--que.end());
		}
	}
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值