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)


*/



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