P7568 「MCOI-05」追杀

原题

首先这题比较重要的一点是要往暴力去想,因为我们发现\(m\)的值很小,而且这个操作是没有合并性的,即不能通过是否存在某个操作来判断全部成员的生存情况

我们先考虑一个比较暴力的做法,暴力枚举对于每个点\(u\)如果在\(t\)时刻死亡会影响哪些操作,对于操作我们暂时先暴力的\(O(n)\)枚举,容易发现这个复杂度是\(O(n^2m)\),貌似比暴力还要差


我们考虑怎么优化,对于某一个\(u\)点若最终会死亡,则必然存在血量\(=1\)\(=0\)的情况(废话),我设\(u\)点血量\(=1\)的时间为\([l_i,r_i]\),我们发现如果在范围\([1,l_i]\)内我们对\(u\)点进行攻击,则显然\(t=r_i\);而如果我们在\([l_i,r_i]\)的范围内进行攻击,则\(t\)就是他的攻击时间,对于所有以\(x\)作为攻击者的所有操作时间位于区间\([t,r_i]\)不会被记为贡献

这么做看起来貌似没有优化做法,但我们发现对于所有以\(x\)作为攻击者的所有操作时间位于区间\([t,r_i]\)中所有有用的操作最多只有\(3m\)个,因为一个人血量最多\(3\)条,所以我们可以把复杂度优化到\(O(nm^2)\),复杂度和暴力同级


我们发现对于每一个点\(u\),当\(t \in [l_i,r_i]\)时,我们会重复计算\([t,r_i],[t-1,r_i],[t-2,r_i]...\)的情况,于是我们对于每一个攻击者\(u\),按时间倒序考虑所有情况,每次把某一个操作在之间的基础上操作,之前的操作要保存信息,可以做到复杂度\(O(m^3)\)


我们发现枚举\(u\)点是不是很好优化掉的,暴力递推同理,于是我们考虑怎么尽量少的暴力递推。我们发现如果钦定某一次操作\((x,y)\)失效,换言之在这次操作前让\(x\)死亡,那我们只需要影响这一个操作即可暴力递推,只需要在遇到这次操作后\(x\)的生命值改成\(0\)即可(这里是我一开始不理解正解的原因),这样我们对于每一个有效的操作,把这个操作去掉后暴力记录答案,最终乘上\(DreamXD\)选择的方案即可

最终复杂度\(O(nm)\),因为有效的操作个数只有\(O(m)\)

解释起来可能不是很清楚,下面给一下代码

#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define pcn putchar('\n')
#define ll long long
#define MP make_pair
#define fi first
#define se second
#define gsize(x) ((int)(x).size())
#define Min(a, b) (a = min(a, b))
#define Max(a, b) (a = max(a, b))
#define For(i, j, k) for(int i = (j), END##i = (k); i <= END##i; ++ i)
#define For__(i, j, k) for(int i = (j), END##i = (k); i >= END##i; -- i)
#define Fore(i, j, k) for(int i = (j); i; i = (k))
//#define random(l, r) ((ll)(rnd() % (r - l + 1)) + l)
using namespace std;

namespace IO {
	template <typename T> T read(T &num){
	    num = 0; T f = 1; char c = ' '; while(c < 48 || c > 57) if((c = getchar()) == '-') f = -1;
	    while(c >= 48 && c <= 57) num = (num << 1) + (num << 3) + (c ^ 48), c = getchar();
	    return num *= f;
	}
	template <typename T> void Write(T x){
	    if(x < 0) putchar('-'), x = -x; if(x == 0){putchar('0'); return ;}
	    if(x > 9) Write(x / 10); putchar('0' + x % 10); return ;
	}
	void putc(string s){ int len = s.size() - 1; For(i, 0, len) putchar(s[ i ]); }
	template <typename T> void write(T x, string s = "\0"){ Write( x ), putc( s ); }
}
using namespace IO;

/* ====================================== */

const int maxn = 6e4 + 50;
const int maxm = 1e3 + 50;

int n, m;
pair<int, int> a[ maxn ];
int he[ maxn ];
int lst[ maxn ], rge[ maxn ];
int ans[ maxn ];

inline void solve(int ct){ //计算如果强制让ct这个操作失效,最终的答案 
	For(i, 1, m) he[ i ] = 3;
	For(i, 1, n){
		if(i == ct){
			he[ a[ i ].fi ] = 0; // 这里一定要注意,因为我们钦定ct这个操作失效,因此我们要把x的血量改为0,这样我们只用确定第一个失效的操作,就可以求出对后面的影响 
			continue;
		}
		if(!he[ a[ i ].fi ] || !he[ a[ i ].se ]) continue;
		-- he[ a[ i ].se ];
	}
}

void mian(){
	read(n), read(m);
	For(i, 1, n) read(a[ i ].fi), read(a[ i ].se);
	For(i, 1, m) he[ i ] = 3;
	For(i, 1, n){
		if(!he[ a[ i ].fi ] || !he[ a[ i ].se ]) continue;
		if(he[ a[ i ].fi ] == 1){
			rge[ i ] = i - lst[ a[ i ].fi ];
			lst[ a[ i ].fi ] = i;
		}
		-- he[ a[ i ].se ];
	}// 先考虑如果XD不击杀任何人,最后存活的人的数量 
	int lve = 0;
	For(i, 1, m) if(he[ i ] > 0) ++ lve; // 存活人的数量 
	For(i, 1, m){
		if(he[ i ] == 1) ans[ lve - 1 ] += n - lst[ i ] + 1; // 考虑XD如果在i这个人1滴血,并且把所有要击杀的玩家都击杀后,XD把他击杀的方案数 
		
		if(he[ i ] > 1) ans[ lve ] += n + 1;
		if(!he[ i ]) ans[ lve ] += n - lst[ i ] + 1; // 考虑XD如果进行了一次无用的击杀(击杀了超过1滴血的人 或 击杀了已经死亡的人)的方案数 
	}
	For(i, 1, n){
		if(!rge[ i ]) continue; // 这里外层循环是O(m)的,因为每个人最多被有效击杀3m次 
		solve(i);
		lve = 0;
		For(j, 1, m) if(he[ j ] > 0) ++ lve;
		ans[ lve ] += rge[ i ]; // 统计i的方案数 
	}
	For(i, 0, m) write(ans[ i ], " ");
}

void init(){

}

int main() {

#ifdef ONLINE_JUDGE
#else
	freopen("data.in", "r", stdin);
//	freopen("data.out", "w", stdout);
#endif

	int T = 1;
//	read(T);
	while(T --){
		init();
		mian();
	}

	return 0;
}
posted @ 2023-09-05 20:34  FOX_konata  阅读(72)  评论(0)    收藏  举报