[比赛|考试] 9.17下午考试

9.17 下午考试

又tm炸了。。。

100/300 (100,0,0) rank10

T1秒切不管,T2T3思索2h无果,//xjb写的暴力还tm写炸了又tm写检查了(爆粗口警告)

经验/教训/反思:OI赛先写暴力先写暴力先写暴力

T1

给出 m 个数 a[1],a[2],…,a[m],求1~n 中有多少数不是 a[1],a[2],…,a[m]的倍数。m是20,n和a[i]是10的9

一个简单的容斥(幸好我讲过),秒切。

\(\displaystyle \mathrm{Ans}=n-\sum_{i}\left\lfloor\frac n i\right\rfloor+\sum_{i}\sum_{j}\left\lfloor\frac n {ij}\right\rfloor-\sum_{i}\sum_j\sum_k\left\lfloor\frac n {ijk}\right\rfloor+\cdots\)

要不是校内考试不让用python,否则就会T的飞起。。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

//这个程序本来是会爆的,我们需要
//1.开long long 2.可行性剪枝,当当前的lcm大于n时候直接返回,因为除以后变成0, 3.从大到小sort,可以减少搜索树的枝数。
//并且保证了lcm在1e9范围内,不会爆。
//byebye python3

long long n, a[25], ans;
int m;

long long gcd(long long x, long long y)
{
	if (y == 0)
		return x;
	else
		return gcd(y, x % y);
}

long long get_lcm(long long x, long long y)
{
	return x * y / gcd(x, y);
}

void search(int pos, int tot, long long lcm)
{
	if (lcm > n)
		return;
	if (pos == m + 1)
	{
		if (tot & 1)
			ans -= n / lcm;
		else
			ans += n / lcm;
		return;
	}
	search(pos + 1, tot, lcm);
	search(pos + 1, tot + 1, get_lcm(lcm, a[pos]));
	
}

int main()
{
	freopen("count.in", "r", stdin);
	freopen("count.out", "w", stdout);
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
		cin >> a[i];
	sort(a + 1, a + 1 + m, greater<int>());
	search(1, 0, 1);
	cout << ans << endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2

一个区间的价值定义为该区间中的最大值减最小值 给定 n 个数,求所有区间价值中,第 k 大值为多少。n是40万

正解是二分那个第k大的值,然后求出有多少个区间是大于那个值的。反正就是二分的那个套路。

至于求出多少个区间的价值大于那个值可以用单调队列或者是稀疏表做,这里单调队列要手写deque否则T得飞起。我们枚举右端点,对于一个确定的右端点,对于所有的左端点,区间的价值是随着左端点向右移动而递减的。所以我们可以得到一个确切的位置满足在这个位置左边的左端点,区间价值大于二分出来的那个mid,右边的 小于。并且这个位置随着右端点的移动是不减的,所以我们可以单调队列维护一个区间最大和最小来维护这个位置

(感觉对单调队列更透彻点了)当然稀疏表也行

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;

int a[400010];
int n, maxn;
long long k;

struct deque
{int aa[1000010];
    int head,tail;
    deque(){head=1;tail=1;}//[head,tail)
    bool empty(){return head==tail;}
    void push_back(int x){aa[tail++]=x;}
    void pop_back(){tail--;}
    void pop_front(){head++;}
    int back(){return aa[tail-1];}
    int front(){return aa[head];}
    void clear(){memset(aa,0,sizeof(aa));head=tail=1;}
}qmin, qmax;

long long get(int x)//找所有>=x的数的个数
{
	//对于所有\le l的元素都有的
//	printf("get %d\n", x);
	long long ans = 0;
	qmin.clear();
	qmax.clear();
	for (int l = 1, r = 1; r <= n; r++)
	{
		while (!qmin.empty() && a[qmin.back()] >= a[r])
			qmin.pop_back();
		while (!qmax.empty() && a[qmax.back()] <= a[r])
			qmax.pop_back();
		qmin.push_back(r);
		qmax.push_back(r);
		while (!qmax.empty() && !qmin.empty() && a[qmax.front()] - a[qmin.front()] >= x)
		{
			l++;
//			printf("l = %d, maxf = %d, minf = %d\n", l, qmax.front(), qmin.front());
			if (qmax.front() < l)
				qmax.pop_front();
			if (qmin.front() < l)
				qmin.pop_front();
		}
		ans += l - 1;
	}
//	printf("get %d = %lld\n", x, ans);
	return ans;
}

int main()
{
	freopen("kth.in", "r", stdin);
	freopen("kth.out", "w", stdout);
	scanf("%d%lld", &n, &k);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		maxn = max(a[i], maxn);
	}
	int l = 0, r = maxn;
	while (l < r)
	{
		int mid = (l + r + 1) / 2;
		if (get(mid) >= k)//表示>=x的数是否>=k
			l = mid;
		else
			r = mid - 1;
	}
	printf("%d\n", l);
	return 0;
}

T3

有 n 个堡垒排成一排构成了一条防御线。现在需要将 n 个武器放入这 n 个堡垒中,每个 堡垒放一个,每个武器有攻击力和战场贡献值两个属性。 由于这 n 个武器都不是人为操控的,所以会对其某半径内所有单位进行攻击,而这就导 致某些堡垒的互相攻击。现在发现第 i 个堡垒会和第 j 个堡垒互相攻击当且仅当|i-j|<=r, 且攻击力较低的武器和他所在的堡垒会破损。 现在你需要给出一种武器分配方案使得未破损武器的战场贡献值总和最大。为了方便你 只需输出战场贡献值总和的最大值即可。 多组数据(T$\le$10),n是5000

首先,思考这样一个问题,如果我们给定了哪些武器被钦定,那么判断这个钦定方案是否合法。考虑贪心。把钦定的武器和当炮灰的武器分别按照攻击力从小到大排序。从左到右安排这个序列,那么每选择一个钦定的武器就要选择r个当炮灰的武器。不难想到一个式子,对于一个武器,在被钦定武器中攻击力是第i小的,在所有武器中攻击力是第j小的,那么\(j\ge i\times(r+1)\)。当然最后一个武器除外(首先是因为他最大最透彻他必须被钦定,还有就是因为他后面不用选r个武器当做它的炮灰了)

所以我们把所有武器按照攻击力从小到大来排序,然后开始DP。(注意这里和官方题解略有不同)设f[i][j]代表决策了前j个武器,其中钦定了i个武器,且第j个武器必须被钦定,此时的最大价值。根据上面的推断,这里要满足j >= i * (r + 1) || j == n。对不合法的决策我么直接设为\(-\infty\)即可。转移方程就是f[i][j] = max{f[i - 1][k]} + contribute[j](k < j)。注意到取最大值这里可以用前缀最大值优化,即每次统计一个sum[i][j] = max(sum[i][j - 1], f[i][j]),转移直接从sum[i - 1][j - 1]转移。最后答案为所有的f[i][j]最大值。详见代码。

(代码中错误修正了)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

struct weapon
{
	int attack, contribution;
}a[5010];

int ans, n, r;

int f[5010][5010], sum[5010][5010];

bool cmp(const weapon &x, const weapon &y)
{
	return x.attack < y.attack;
}

void work()
{
	memset(a, 0, sizeof(a));
	
	scanf("%d%d", &n, &r);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i].attack);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i].contribution);
	sort(a + 1, a + 1 + n, cmp);
	memset(f, 0xc0, sizeof(f));
	memset(sum, 0, sizeof(sum));
	f[0][0] = 0;
	
	for (int i = 1; i <= n; i++)//选定了多少武器
		for (int j = 1; j <= n; j++)//所有武器
		{
			if (j >= i * (r + 1) || j == n)
			{
				f[i][j] = sum[i - 1][j - 1] + a[j].contribution;
				sum[i][j] = max(sum[i][j - 1], f[i][j]);
				ans = max(ans, f[i][j]);
			}
		}
	printf("%d\n", ans);
}

int main()
{
	freopen("submax.in", "r", stdin);
	freopen("submax.out", "w", stdout);
	int t;
	scanf("%d", &t);
	while (t--)
	{
		work();
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}
posted @ 2018-09-18 06:08  ghj1222  阅读(353)  评论(0编辑  收藏  举报