第一次出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)
*/