P7568 「MCOI-05」追杀

原题

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

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


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

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


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


我们发现枚举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 @   FOX_konata  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示