BestCoder Round92 出题人之 题解报告与代码 ^_^

第一次出BC真是有些小激动啊2333~


当然我还是很懒的,这次出BC的过程大体就像是我在之前所说过的:

上学期想出BC——老师说没问题,下场你出,于是我准备出BC——突然有情况,时间延至年后,于是我开始玩……——Camp时老师突然告诉我下场比赛轮到我——开始懵逼——花了前后两个多星期时间把BC准备好。


要准备中英文题面、hack的checker,小数据和大数据,还要平衡题目难度和题目类型,以及解题报告。果然没那么轻松啊~~

好在题目已经出完了,希望大家还满意。





整体上的题解——

1001:
显然,对于每一种类型的课程,我们只会选择翘掉 翘课价值最大的前2节课。
于是,最方便的做法,是使用map<string, int>first, second来实现。
即:
for(i = 1 ~ n)
{
    scanf("%s%d", s, &v);
    gmax(second[s], v);
    if (second[s] > first[s])swap(second[s], first[s]);
}
然后把两个map中的权值全部加到sum中即可。

1002:
非常感谢elfness对出题想法的帮助!就是这题,为了保障本次BC足够友好,换掉了另外一个质量还可以的不过大概难一些的题,让更多人2题保本xD。
首先,因为朋友关系只能是在男羊和女羊之间的,所以这是一个二分图。
然后,我们发现每个序列都满足一端为男羊,另外一端为女羊,于是我们可以按照"女羊A,男羊B,女羊C,男羊D"的方式计数,在最后使得答案*2就好。
a[x]存是男羊x朋友的所有女羊,cnt[y]存女羊y拥有的男羊朋友数。于是:
for (int x = 1; x <= n; ++x)                //枚举男羊B
{
    LL onepoint = a[x].size() - 1;        //除去女羊C,女羊A的可能方案数为a[x].size() - 1
    for (auto y : a[x])                                //枚举女羊C,这两层for循环其实只枚举了k条边,复杂度为o(n+m+k)
    {
        ans += (cnt[y] - 1) * onepoint;    //显然除了男羊B,其他男羊都可以作为男羊D,计数为cnt[y] - 1
    }
}最后ans * 2就是答案啦。
PS:这题会爆int

1003:
首先,因为字符不是'2'就是'3',所以我们可以把字符串当做一个全部都是'3'的串,然后有若干的'2'插入到了某些位置。
显然,我们交换相邻的'2'与'2'或者相邻的'3'与'3'是没有意义的,我们只会进行相邻'2'与'3'之间的交换。因此,所有'2'的相对前后关系其实是不会变化的。
做了这些比较基础的分析之后,基于数据规模极小,我们可以用
1.处理到第几个'2'
2.最后一个'2'停留在什么位置,如果当前的'2'与上一个'2'距离相差>=2时则对答案+1
3.呃喵的剩余交换次数是多少
4.当前已经成功得到几个"233"
四个要素的大小,最坏情况下分别是n、n、m、n级别的数,我们随便以3个要素作为下标对应状态,使得第4个要素最优做DP. 转移的时候步长也是不超过2m的,所以很容易就可以得出复杂度为O(n * n * m/2 * m)的算法,这个对于本题的时限和数据,没有什么作死写法的话是可以顺利AC的。
这题本来是放在02的,可以用BZG式爆搜通过,但是后来被调整到03,也相应卡掉了大力爆搜。
最后还要提下,csy老师提供了一个复杂度更加优秀的O(n * n * n * 3)的做法——
考虑最后形成的串是'2'与'3'归并排序后的结果。
于是我们显然需要知道——
1.当前选了多少个2
2.当前选了多少个3
3.当前用了多少次交换
4.影响决策的状态,这里有3种情况——
    a.不存在前一个'2',或者前一个'2'后面已经有了足够的'3',记做状态0
    b.前一个'2'后面只有0个'3',记做状态1
    c.前一个'2'后面只有1个'3',记做状态2
用g2与g3表示2与3个个数,用p[]记录所有2的位置,于是就可以有——
for(i)for(j)for(k)for(t)if(~f[i][j][k][t])
{
    if (i < g2)//我们可以考虑接下来放置一个'2'
    {
        int sum = k + abs(i + j + 1 - p[i + 1]);
        if (sum <= m)gmax(f[i + 1][j][sum][1], f[i][j][k][t]);
    }
    if (j < g3)//我们可以考虑接下来放置一个'3'
    {
        int sta = t; int cnt = f[i][j][k][t];
        if (sta)
        {
            if (sta < 2)++sta;
            else sta = 0, ++cnt;
        }
        gmax(f[i][j + 1][k][sta], cnt);
    }
}最后在f[g2][g3][0~m][0~2]中更新答案。
PS:由于数据组数过多,直接memset复杂度是1e6*1000级别,会导致TLE哦~ 其实初测已经给了很多组数,目的就是让MS的代码意识到其可能会FST.

1004:
如果给出时间点范围只有T,那么我们可以枚举每个时间点,用f[i]表示我们考虑了时间为[1, i]的所有决策下能够玩的最多游戏数,则有f[i] = max(max(f[i - d[j]] + 1), f[i - 1]),要求[i-d[j]+1,i]都是翘课时间,且[i-d[j]+1]为游戏j的兴趣时间(可以用前缀维护),这样我们就得到了一个复杂度为O(Tn)的算法。

然而这道题给出的时间点范围高达1e9,于是需要引入贪心思想,
很显然,对于任意一个时间点,如果是以这个时间点为最早时间开始一局游戏的话,显然,只要一局结束时间依然落在游戏的感兴趣时间范围内的话,我们会选择游戏时间最短的游戏。这是基于"所有游戏局的收益都为1" 的条件,局部最优且不影响全局最优,符合贪心条件。
而同理,如果我们之前开始了一局游戏,还有一个剩余完成时间,然而倘若以此时间点为开端进行某局游戏,两者的剩余游戏时间相比,越小越优。即我们任何时候都会选择剩余完成时间最少的游戏(当然要求合法)。
同时,虽然时间点很多,但是因为游戏感兴趣区间段的划分作用,实际最多只有2m级别的区间段,使得操作变得可能。有一点需要注意,我们需要保证一个游戏的执行是合法的。于是可以按照枚举翘课时间段的方式展开,这首先保证了是在翘课时间段内进行游戏。而如果一个游戏的参数是(l, r, d),我们把该游戏第一个感兴趣的时间点定在l,该游戏第一个不感兴趣的时间点定在r - d + 2,在这2个时间点,分别在另一个multiset中维护d的插入与删除,就可以使得,只要我们是在可以选取该游戏的时间点开始某局游戏,就一定满足游戏可以正常进行的合法性。

接下来就是具体实现了。标程是使用STL中的set与map实现。具体思路如下:
1,呃喵的翘课时间段如果相邻两段不连续的话,即L[i + 1] != R[i],这两段之间是不会有游戏局的延续的。
    于是我们先把本质连续的翘课区间段做合并,然后对于每个翘课区间段,独立实现操作。
2,对于一个翘课区间段[ L[i], R[i] ],其可能会随着游戏感兴趣|不感兴趣 时间点的作用,被划分为多个状态不同的区间段。
    我们需要做这么几个事情――
    ①先把[R[i - 1], L[i]]时间范围内的"某个游戏开始感兴趣了","某个游戏不再感兴趣了"的状态生效。
        因为我们的关键是基于游戏局的时长,所以可以用multiset来维护,以实现插入与删除的双重操作。
    ②把[L[i], R[i]]时间范围内的"某个游戏开始感兴趣了","某个游戏不再感兴趣了"的状态按照时间先后依次生效。
    ③对于一个确定状态的时间段。
        先保证已经更新好游戏的感兴趣状态,知道当前最短游戏时间d。
        再考虑之前未完成的游戏(肯定是上个状态最优的,可能不存在)的剩余完成时间,与d做比较,选择较小者作为当前时间段一开始的选择。
        对于接下来的剩余时间,我们全部去进行游戏d,如果无法完成整数局,记录剩余游戏完成时间。
       
更多可以参考标程,总之想法较简单,细节可额有一些,考验了代码实现能力。




具体代码,包括了数据制造,对拍,标程等。

P1 翘课计划 Skip The Class

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 0, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n;
void datamaker_pre()
{
	srand(time(0));
	freopen("Skip the Class_pre.in", "w", stdout);
	casenum = 1000; printf("%d\n", casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		int n = rand() % 10 + 1; printf("%d\n", n);
		for (int i = 1; i <= n; ++i)
		{
			int l = rand() % 3 + 1;
			for (int j = 0; j < l; ++j)printf("%c", rand() % 5 + 'a');
			int v = rand() % 1000 + 1;
			printf(" %d\n", v);
		}
	}
}
void datamaker()
{
	srand(time(0));
	freopen("Skip the Class.in", "w", stdout);
	casenum = 1000; printf("%d\n", casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		int n = rand() % 100 + 1; printf("%d\n", n);
		for (int i = 1; i <= n; ++i)
		{
			int l = rand() % 3 + 1;
			for (int j = 0; j < l; ++j)printf("%c", rand() % 5 + 'a');
			int v = rand() % 1000 + 1;
			printf(" %d\n", v);
		}
	}
}
int main()
{
	//datamaker_pre(); return 0;
	//freopen("Skip the Class_pre.in", "r", stdin); freopen("Skip the Class_pre.out", "w", stdout);
	//datamaker(); return 0;
	//freopen("Skip the Class.in", "r", stdin); freopen("Skip the Class.out", "w", stdout);
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		map<string, int> first, second;
		first.clear();
		second.clear();
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i)
		{
			char s[12]; int v;
			scanf("%s%d", s, &v);
			gmax(second[s], v);
			if (second[s] > first[s])swap(second[s], first[s]);
		}
		int sum = 0;
		for (auto it : first)sum += it.second;
		for (auto it : second)sum += it.second;
		printf("%d\n", sum);
	}
	return 0;
}
/*
【分析】
这道题其实就是对每种字符串保留最大的两个价值就好。这里用map实现比较方便。

*/


P2 数羊睡好觉 Count the Sheep

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n, m, k;
vector<int>a[N];
int cnt[N];


set< pair<int, int> >sot;
void datamaker()
{
	srand(time(0));
	freopen("Count the Sheep.in", "w", stdout);
	casenum = 1000; printf("%d\n", casenum);
	for (int casei = 1; casei <= casenum; ++casei)
	{
		sot.clear();
		if (casei <= 300)
		{
			n = rand() % 100 + 1; m = rand() % 100 + 1; k = rand() % min(n * m, 100);
		}
		else if (casei <= 990)
		{
			n = rand() % 1000 + 1; m = rand() % 1000 + 1; k = rand() % min(n * m, 1000);
		}
		else if (casei <= 991)
		{
			n = m = 200; k = 40000;
		}
		else if (casei <= 992)
		{
			n = m = 400; k = 100000;
		}
		else
		{
			n = m = k = (casei - 990) * 10000;
		}
		printf("%d %d %d\n", n, m, k);
		for (int i = 1; i <= k; ++i)
		{
			int x, y;
			while(1)
			{
				x = rand() % n + 1; 
				y = rand() % m + 1;
				if (!sot.count({ x,y }))break;
			}
			sot.insert({ x,y });
			printf("%d %d\n", x, y);
		}
	}
}


void datamaker_pre()
{
	srand(time(0));
	freopen("Count the Sheep_pre.in", "w", stdout);
	casenum = 100; printf("%d\n", casenum);
	for (int casei = 1; casei <= casenum; ++casei)
	{
		sot.clear();
		n = rand() % 50 + 1; m = rand() % 50 + 1; k = rand() % min(n * m, 50);
		printf("%d %d %d\n", n, m, k);
		for (int i = 1; i <= k; ++i)
		{
			int x, y;
			while (1)
			{
				x = rand() % n + 1;
				y = rand() % m + 1;
				if (!sot.count({ x,y }))break;
			}
			sot.insert({ x,y });
			printf("%d %d\n", x, y);
		}
	}
}

int main()
{
	//datamaker(); return 0;
	//freopen("Count the Sheep.in", "r", stdin); freopen("Count the Sheep.out", "w", stdout);

	//datamaker_pre(); return 0;
	//freopen("Count the Sheep_pre.in", "r", stdin); freopen("Count the Sheep_pre.out", "w", stdout);

	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d%d%d", &n, &m, &k);
		for (int i = max(n, m); i >= 1; --i)a[i].clear(), cnt[i] = 0;
		for (int i = 1; i <= k; ++i)
		{
			int x, y; scanf("%d%d", &x, &y);
			a[x].push_back(y);
			++cnt[y];
		}
		LL ans = 0;
		for (int x = 1; x <= n; ++x)
		{
			LL onepoint = a[x].size() - 1;
			for (auto y : a[x])
			{
				ans += (cnt[y] - 1) * onepoint;
			}
		}
		printf("%lld\n", ans * 2);
	}
	return 0;
}
/*
【分析】
非常感谢elfness对出题想法的帮助!就是这题,为了保障本次BC足够友好,换掉了另外一个质量还可以的不过大概难一些的题,让更多人2题保本xD。
首先,因为朋友关系只能是在男羊和女羊之间的,所以这是一个二分图。
然后,我们发现每个序列都满足一端为男羊,另外一端为女羊,于是我们可以按照"女羊A,男羊B,女羊C,男羊D"的方式计数,在最后使得答案*2就好。
a[x]存是男羊x朋友的所有女羊,cnt[y]存女羊y拥有的男羊朋友数。于是:
for (int x = 1; x <= n; ++x)                //枚举男羊B
{
    LL onepoint = a[x].size() - 1;			//除去女羊C,女羊A的可能方案数为a[x].size() - 1
    for (auto y : a[x])						//枚举女羊C,这两层for循环其实只枚举了k条边,复杂度为o(n+m+k)
    {
        ans += (cnt[y] - 1) * onepoint;		//显然除了男羊B,其他男羊都可以作为男羊D,计数为cnt[y] - 1
    }
}最后ans * 2就是答案啦。
计数时每条边和每个点都只遍历一次,所以复杂度为O(n + m)
PS:这题会爆int

*/



P3 233爱好群 Girls Love 233,程序在solve()函数中哦

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 105, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;
int n, m, g;
char s[N];
int p[N];
void datamaker()
{
	srand(time(0));
	freopen("Girls Love 233.in", "w", stdout);
	casenum = 1000; printf("%d\n", casenum);
	for (casei = 1; casei <= 990; ++casei)
	{
		n = rand() % 20 + 1; m = rand() % 21;
		printf("%d %d\n", n, m);
		for (int i = 1; i <= n; ++i)putchar(rand() % 2 + '2');
		puts("");
	}
	for (casei = 991; casei <= 1000; ++casei)
	{
		n = rand() % 51 + 50; m = rand() % 51 + 50;
		printf("%d %d\n", n, m);
		for (int i = 1; i <= n; ++i)putchar(rand() % 2 + '2');
		puts("");
	}
}

void datamaker_pre()
{
	srand(time(0));
	freopen("Girls Love 233_pre.in", "w", stdout);
	casenum = 500; printf("%d\n", casenum);
	for (casei = 1; casei <= 500; ++casei)
	{
		n = rand() % 12 + 1; m = rand() % 13;
		printf("%d %d\n", n, m);
		for (int i = 1; i <= n; ++i)putchar(rand() % 2 + '2');
		puts("");
	}
}

int dfn[N][N][N];
int f[N][N][N];
int dfs(int k, int lp, int tim)
{
	if (k > g)return (n - lp >= 2);
	if (dfn[k][lp][tim] == casei)return f[k][lp][tim];
	dfn[k][lp][tim] = casei;
	f[k][lp][tim] = -1e9;
	int l = max(lp + 1, p[k] - tim);
	int r = min(n, p[k] + tim);
	for (int i = l; i <= r; ++i)
	{
		int cost = abs(p[k] - i);
		gmax(f[k][lp][tim], dfs(k + 1, i, tim - cost) + (i - lp >= 3) * (k > 1));
	}
	return f[k][lp][tim];
}

void solve()
{
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d%d", &n, &m); m /= 2;
		scanf("%s", s + 1);
		g = 0; for (int i = 1; i <= n; ++i)if (s[i] == '2')p[++g] = i;
		if (g == 0) { puts("0"); continue; }
		int ans = dfs(1, 0, m);
		printf("%d\n", ans);
	}
}

int main()
{
	//datamaker(); return 0;
	//freopen("Girls Love 233.in", "r", stdin); freopen("Girls Love 233.out", "w", stdout);

	//datamaker_pre(); return 0;
	//freopen("Girls Love 233_pre.in", "r", stdin); freopen("Girls Love 233_pre.out", "w", stdout);
	solve();
	return 0;
}
/*
【分析】
这道题有些难于思考。
一开始不妨使得m/=2,这时的m就是我们的最大交换次数。
首先我们可以得到一个贪心原则:我们肯定不会改变所有给定'2'的先后顺序,即我们只会交换相邻'2'和'3'之间的位置。
于是,我们预处理出p[i]表示第i个'2'的位置为p[i]。
而且,我们应该观察到,在这道题中,m非常小,这个可以作为我们的突破口,因为每个'2'就算经过交换,距初始位置p[]的距离也不会超过m。

在这个基础上,我们构想一个DP:
用f[i][j][k]表示――
我们已经处理了从左到右数的第i个'2',已经用了j次交换,且第i个'2'的最终位置k,在这种状态下的最多的"233"的个数。
那么我们有一个DP转移方程――
f[i][lj+j][p[i]±j]=max(f[i-1][lj][lp(all possible positions)] + (p[i]±j - lp >= 3), p[i]±j > lp
意思是――
我们处理了前i个'2'的位置,也一定是通过前i-1个'2'的位置的状态所转移而来的。
然后处理前i-1个'2'的位置时,用了lj次交换,现在这第i个'2',我们额外用了j次交换,
第i-1个'2'的合法位置范围,是一个很小的区间范围(不会超过2m),这里暴力枚举即可。
然而,因为要保证我们贪心原则的正确性,当前'2'的位置必须严格比前一个大,于是p[i]±j > lp。
然后,如果相邻的两个'2'的距离>=3,那么我们便会多出一个233。

*/


O(n * n * n * 3)代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 105, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n, m;
char s[N];
int f[N][N][N][3];
int p[N];
int main()
{
	//fre();
	scanf("%d", &casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		scanf("%d%d", &n, &m); m /= 2;
		scanf("%s", s + 1);
		int g2 = 0; for (int i = 1; i <= n; ++i)if (s[i] == '2')p[++g2] = i;
		int g3 = n - g2; 
		
		for (int i = 0; i <= g2; ++i)
		{
			for (int j = 0; j <= g3; ++j)
			{
				for (int k = 0; k <= m; ++k)
				{
					for (int t = 0; t < 3; ++t)f[i][j][k][t] = -1;
				}
			}
		}

		f[0][0][0][0] = 0; 
		for (int i = 0; i <= g2; ++i)
		{
			for (int j = 0; j <= g3; ++j)
			{
				for (int k = 0; k <= m; ++k)
				{
					for (int t = 0; t < 3; ++t)if(~f[i][j][k][t])
					{
						//我们可以考虑接下来放置一个'2'
						if (i < g2)
						{
							int sum = k + abs(i + j + 1 - p[i + 1]);
							if (sum <= m)gmax(f[i + 1][j][sum][1], f[i][j][k][t]);
						}
						//我们可以考虑接下来放置一个'3'
						if (j < g3)
						{
							int sta = t; int cnt = f[i][j][k][t];
							if (sta)
							{
								if (sta < 2)++sta;
								else sta = 0, ++cnt;
							}
							gmax(f[i][j + 1][k][sta], cnt);
						}
					}
				}
			}
		}
		int ans = 0;
		for (int k = 0; k <= m; ++k)
		{
			for (int t = 0; t < 3; ++t)
			{
				gmax(ans, f[g2][g3][k][t]);
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}



P4 游戏安排 Game Arrangement,程序在solve()中哦~

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
#include<functional>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 10010, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n, m;
int L[N], R[N];
map< int, vector<int> >mop;
multiset<int>sot;
int solve()
{
	int nn; scanf("%d%d", &nn, &m);

	R[n = 0] = -100;
	for (int i = 1; i <= nn; ++i)
	{
		int ll, rr; scanf("%d%d", &ll, &rr);
		if (ll == R[n] + 1)R[n] = rr;
		else { L[++n] = ll; R[n] = rr; }
	}

	mop.clear();
	for (int i = 1; i <= n; ++i)mop[R[i] + 1].clear();
	for (int i = 1; i <= m; ++i)
	{
		int l, r, d;
		scanf("%d%d%d", &l, &r, &d);
		if (r - l + 1 < d)continue;
		mop[l].push_back(d);			//最早的可行开始时间
		mop[r - d + 2].push_back(-d);	//最早的非法开始时间
	}

	sot.clear();
	auto t = mop.begin();
	int ans = 0;
	//按照从前向后的顺序,依次枚举空闲时间段,因为保证了空闲时间段的非连续性,所以其相互可以看做是独立的
	for (int i = 1; i <= n; ++i)
	{
		//第一步,更新可玩游戏集合
		while (t != mop.end() && t->first <= L[i])
		{
			for (auto d : t->second)
			{
				if (d > 0)sot.insert(d);
				else sot.erase(sot.find(-d));
			} ++t;
		}
		//第二步,对于相邻可玩游戏集合不变动时期做变动
		int pre = L[i];	//pre表示前置时间点
		int rst = inf;	//rst表示之前未完成的最优任务所剩余时间
		while (t != mop.end() && t->first <= R[i] + 1)
		{
			//step1,处理此时间点之前的决策
			int len = t->first - pre;
			pre = t->first;
			if (rst != inf) //如果之前存在一个未完成较优任务,则我们优先处理这个任务。
			{
				int can = min(rst, len);
				len -= can;
				rst -= can;
				if (rst == 0)
				{
					++ans;
					rst = inf;
				}
			}
			if (rst == inf && !sot.empty())//不光是处理未完成最优任务,我们还需要考虑区间段的整体选择
			{
				int d = *sot.begin();
				ans += len / d;
				rst = len % d; 
				if (rst)rst = d - rst; else rst = inf;
			}
			//step2,为该时间点之后的决策做预处理
			for (auto d : t->second)
			{
				if (d > 0)sot.insert(d);
				else sot.erase(sot.find(-d));
			} ++t;
			if (!sot.empty())gmin(rst, *sot.begin());
		}
	}
	return ans;
}

const int T = 2001;
const int TT = 1000000000;
int ok[T], f[T];
int l[N], r[N], d[N];
int bf()
{
	for (int i = 0; i < T; ++i)ok[i] = 0;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
	{
		int ll, rr; scanf("%d%d", &ll, &rr);
		for (int j = ll; j <= rr; ++j)ok[j] = 1;
	}
	for (int i = 1; i < T; ++i)ok[i] *= (ok[i - 1] + 1);
	for (int i = 1; i <= m; ++i)scanf("%d%d%d", &l[i], &r[i], &d[i]);
	for (int i = 1; i < T; ++i)
	{
		f[i] = f[i - 1];
		for (int j = 1; j <= m; ++j)
		{
			int p = i - d[j] + 1;
			if (p >= l[j] && i <= r[j] && ok[i] >= d[j])gmax(f[i], f[p - 1] + 1);
		}
	}
	return f[T - 1];
}

void datamaker()
{
	srand(time(0));
	freopen("Game Arrangement.in", "w", stdout);
	casenum = 1000; printf("%d\n", casenum);
	for (casei = 1; casei <= 990; ++casei)
	{
		n = rand() % 100 + 1; m = rand() % 100 + 1;
		printf("%d %d\n", n, m);
		set<int>S;
		while (S.size() <= n * 2)
		{
			int x = rand() * rand() % TT + 1;
			S.insert(x);
		}
		auto it = S.begin();
		for (int i = 1; i <= n; ++i)
		{
			printf("%d ", *it); ++it;
			printf("%d\n", *it); ++it;
		}
		for (int i = 1; i <= m; ++i)
		{
			l[i] = rand() * rand() % (TT / 2) + 1;
			r[i] = l[i] + rand() * rand() % (TT / 2);
			d[i] = rand() * rand() % (TT / 10) + 1;
			printf("%d %d %d\n", l[i], r[i], d[i]);
		}
	}
	for (casei = 991; casei <= 1000; ++casei)
	{
		n = (casei - 990) * 1000; m = (casei - 990) * 1000;
		printf("%d %d\n", n, m);
		set<int>S;
		while (S.size() <= n * 2)
		{
			int x = rand() * rand() % TT + 1;
			S.insert(x);
		}
		auto it = S.begin();
		for (int i = 1; i <= n; ++i)
		{
			printf("%d ", *it); ++it;
			printf("%d\n", *it); ++it;
		}
		for (int i = 1; i <= m; ++i)
		{
			l[i] = rand() * rand() % (TT / 2) + 1;
			r[i] = l[i] + rand() * rand() % (TT / 2);
			d[i] = rand() * rand() % (TT / 10) + 1;
			printf("%d %d %d\n", l[i], r[i], d[i]);
		}
	}
}

void datamaker_pre()
{
	int TT = 1200;
	srand(time(0));
	freopen("Game Arrangement_pre.in", "w", stdout);
	casenum = 500; printf("%d\n", casenum);
	for (casei = 1; casei <= casenum; ++casei)
	{
		n = rand() % 500 + 1; m = rand() % 500 + 1;
		printf("%d %d\n", n, m);
		set<int>S;
		while (S.size() <= n * 2)
		{
			int x = rand() * rand() % TT + 1;
			S.insert(x);
		}
		auto it = S.begin();
		for (int i = 1; i <= n; ++i)
		{
			printf("%d ", *it); ++it;
			printf("%d\n", *it); ++it;
		}
		for (int i = 1; i <= m; ++i)
		{
			l[i] = rand() * rand() % (TT / 2) + 1;
			r[i] = l[i] + rand() * rand() % (TT / 2);
			d[i] = rand() * rand() % (TT / 10) + 1;
			printf("%d %d %d\n", l[i], r[i], d[i]);
		}
	}
}
int main()
{

	//freopen("Game Arrangement.in", "r", stdin); freopen("Game Arrangement_.out", "w", stdout);
	//scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei)printf("%d\n", bf()); return 0;

	//datamaker(); return 0;
	//freopen("Game Arrangement.in", "r", stdin); freopen("Game Arrangement.out", "w", stdout);

	//datamaker_pre(); return 0;
	//freopen("Game Arrangement_pre.in", "r", stdin); freopen("Game Arrangement_pre.out", "w", stdout);
	scanf("%d", &casenum); for (casei = 1; casei <= casenum; ++casei)printf("%d\n", solve());
	return 0;
}
/*
【trick&&吐槽】
关于对拍暴力程序。
我们可以使得时间范围很小。
用f[i]表示我们考虑了时间为[1, i]的所有决策下能够玩的最多游戏数
则有f[i] = max(max(f[i - d[j]] + 1), f[i -1]),要求[i-d[j]+1,i]都是翘课时间,且[i-d[j]+1]为游戏j的兴趣时间

【分析】
如果给出时间点范围只有T,那么我们可以枚举每个时间点,用f[i]表示我们考虑了时间为[1, i]的所有决策下能够玩的最多游戏数,则有f[i] = max(max(f[i - d[j]] + 1), f[i - 1]),要求[i-d[j]+1,i]都是翘课时间,且[i-d[j]+1]为游戏j的兴趣时间(可以用前缀维护),这样我们就得到了一个复杂度为O(Tn)的算法。

然而这道题给出的时间点范围高达1e9,于是需要引入贪心思想,
很显然,对于任意一个时间点,如果是以这个时间点为最早时间开始一局游戏的话,显然,只要一局结束时间依然落在游戏的感兴趣时间范围内的话,我们会选择游戏时间最短的游戏。这是基于"所有游戏局的收益都为1" 的条件,局部最优且不影响全局最优,符合贪心条件。
而同理,如果我们之前开始了一局游戏,还有一个剩余完成时间,然而倘若以此时间点为开端进行某局游戏,两者的剩余游戏时间相比,越小越优。即我们任何时候都会选择剩余完成时间最少的游戏(当然要求合法)。
同时,虽然时间点很多,但是因为游戏感兴趣区间段的划分作用,实际最多只有2m级别的区间段,使得操作变得可能。有一点需要注意,我们需要保证一个游戏的执行是合法的。于是可以按照枚举翘课时间段的方式展开,这首先保证了是在翘课时间段内进行游戏。而如果一个游戏的参数是(l, r, d),我们把该游戏第一个感兴趣的时间点定在l,该游戏第一个不感兴趣的时间点定在r - d + 2,在这2个时间点,分别在另一个multiset中维护d的插入与删除,就可以使得,只要我们是在可以选取该游戏的时间点开始某局游戏,就一定满足游戏可以正常进行的合法性。

接下来就是具体实现了。标程是使用STL中的set与map实现。具体思路如下:
1,呃喵的翘课时间段如果相邻两段不连续的话,即L[i + 1] != R[i],这两段之间是不会有游戏局的延续的。
于是我们先把本质连续的翘课区间段做合并,然后对于每个翘课区间段,独立实现操作。
2,对于一个翘课区间段[ L[i], R[i] ],其可能会随着游戏感兴趣|不感兴趣 时间点的作用,被划分为多个状态不同的区间段。
我们需要做这么几个事情――
①先把[R[i - 1], L[i]]时间范围内的"某个游戏开始感兴趣了","某个游戏不再感兴趣了"的状态生效。
因为我们的关键是基于游戏局的时长,所以可以用multiset来维护,以实现插入与删除的双重操作。
②把[L[i], R[i]]时间范围内的"某个游戏开始感兴趣了","某个游戏不再感兴趣了"的状态按照时间先后依次生效。
③对于一个确定状态的时间段。
先保证已经更新好游戏的感兴趣状态,知道当前最短游戏时间d。
再考虑之前未完成的游戏(肯定是上个状态最优的,可能不存在)的剩余完成时间,与d做比较,选择较小者作为当前时间段一开始的选择。
对于接下来的剩余时间,我们全部去进行游戏d,如果无法完成整数局,记录剩余游戏完成时间。

更多可以参考标程,总之想法较简单,细节可额有一些,考验了代码实现能力。

【时间复杂度&&优化】
O(nlogm)


*/



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章