Codeforces Round #545 (Div. 1)

人生第一场Div. 1

结果因为想D想太久不晓得Floyd判环法、C不会拆点、E想了个奇奇怪怪的set+堆+一堆乱七八糟的标记的贼难写的做法滚粗了qwq靠手速上分qwqqq

A. Skyscrapers

将行列各自离散化并记录下每一个值在行离散化时和列离散化时得到的值以及每一行、每一列出现的最大离散化值

对于每一行和每一列考虑其相交格子的两个离散化值,如果它们的差为(Delta),就把它对应行列最大离散化值中较小的+(Delta),最后两者取Max

#include<iostream>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c)){
	if(c == '-') f = 1;
	c = getchar();
    }
    while(isdigit(c)){
	a = a * 10 + c - 48;
	c = getchar();
    }
    return f ? -a : a;
}

#define PII pair < int , int >
int num[1007][1007] , h[1007][1007] , l[1007][1007] , N , M;

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    N = read(); M = read();
    for(int i= 1 ; i <= N ; ++i)
	for(int j = 1 ; j <= M ; ++j)
	    num[i][j] = read();
    vector < PII > vec;
    for(int i = 1 ; i <= N ; ++i){
	vec.clear();
	for(int j = 1 ; j <= M ; ++j)
	    vec.push_back(PII(num[i][j] , j));
	sort(vec.begin() , vec.end());
	int pre = 0 , cnt = 0;
	for(auto t : vec){
	    cnt += pre != t.first;
	    h[i][t.second] = cnt;
	    pre = t.first;
	}
	h[i][0] = cnt;
    }
    for(int i = 1 ; i <= M ; ++i){
	vec.clear();
	for(int j = 1 ; j <= N ; ++j)
	    vec.push_back(PII(num[j][i] , j));
	sort(vec.begin() , vec.end());
	int pre = 0 , cnt = 0;
	for(auto t : vec){
	    cnt += pre != t.first;
	    l[t.second][i] = cnt;
	    pre = t.first;
	}
	l[0][i] = cnt;
    }
    for(int i = 1 ; i <= N ; ++i){
	for(int j = 1 ; j <= M ; ++j)
	    cout << max(h[i][0] + max(h[i][j] , l[i][j]) - h[i][j] , l[0][j] + max(h[i][j] , l[i][j]) - l[i][j]) << ' ';
	cout << endl;
    }
    return 0;
}

B. Camp Schedule

KMP求出最长Border,因为最长的Border是两个串拼在一起时能够节约的最长的串。然后能放即放,最后剩下的乱放

#include<iostream>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<vector>
#define INF 0x3f3f3f3f
//This code is written by Itst
using namespace std;

char s[500007] , t[500007];
int nxt[500007];

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    scanf("%s %s" , s + 1 , t + 1);
    int lS = strlen(s + 1) , lT = strlen(t + 1);
    nxt[0] = -1;
    for(int i = 1 ; i <= lT ; ++i){
	int cur = nxt[i - 1];
	while(cur != -1 && t[cur + 1] != t[i])
	    cur = nxt[cur];
	nxt[i] = cur + 1;
    }
    int cntS0 = 0 , cntS1 = 0 , cntT0 = 0 , cntT1 = 0 , cntCir0 = 0 , cntCir1 = 0;
    for(int i = 1 ; i <= lS ; ++i)
	if(s[i] == '0') ++cntS0;
	else ++cntS1;
    for(int i = 1 ; i <= nxt[lT] ; ++i)
	if(t[i] == '0') ++cntT0;
	else ++cntT1;
    for(int i = nxt[lT] + 1 ; i <= lT ; ++i)
	if(t[i] == '0') ++cntCir0;
	else ++cntCir1;
    if(cntS0 >= cntT0 && cntS1 >= cntT1){
	cntS0 -= cntT0; cntS1 -= cntT1;
	for(int i = 1 ; i <= nxt[lT] ; ++i)
	    putchar(t[i]);
    }
    while(cntS0 >= cntCir0 && cntS1 >= cntCir1){
	cntS0 -= cntCir0; cntS1 -= cntCir1;
	for(int i = nxt[lT] + 1 ; i <= lT ; ++i)
	    putchar(t[i]);
    }
    while(cntS0){putchar('0'); --cntS0;}
    while(cntS1){putchar('1'); --cntS1;}
    return 0;
}

C. Museums Tour

PS:在status->execution time->最后一板可以看到我3993ms的好成绩qwq

拆点,对于一个点(u)拆成(d)个点(u_0,u_1,...,u_{d-1}),对于一条边((u,v))拆成(d)条边((u_i,v_{(i+1) mod d})),然后tarjan求SCC并顺带求出一个SCC中能够到达的博物馆数量,那么也就是要求选择一条从(1_0)所在的SCC开始的路径,使得经过的所有点能够到达的博物馆数量最多。在DAG上记搜一下即可。

注意:DAG上的路径的博物馆数量就是所有点的博物馆数量相加,而且不会重复计算博物馆。因为如果存在路径((u_i,u_j)),那么从(u_j)沿着与这条路径相同的路径绕(d-1)轮肯定会到达(u_i),所以会存在路径((u_j,u_i)),所以对于(forall i , j)(u_i)(u_j)不会存在拓扑先后顺序的关系,所以不会算重。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
//This code is written by Itst
using namespace std;

inline int read(){
	int a = 0;
	char c = getchar();
	while(!isdigit(c))
		c = getchar();
	while(isdigit(c)){
		a = a * 10 + c - 48;
		c = getchar();
	}
	return a;
}

#define id(i,j) ((i - 1) * D + j)
const int MAXN = (1e5 + 1) * 50;
vector < int > ch[MAXN];
struct Edge{
	int end , upEd;
}Ed[MAXN];
int head[MAXN] , dfn[MAXN] , low[MAXN] , stk[MAXN] , sum[MAXN] , in[MAXN] , ans[MAXN];
int cntEd , top , N , M , D , ts , cntSCC;
bool vis[MAXN] , ins[MAXN] , open[MAXN] , mrk[100003];

inline void addEd(int a , int b){
	Ed[++cntEd] = (Edge){b , head[a]};
	head[a] = cntEd;
}

void pop(int x){
	++cntSCC;
	vector < int > nd;
	do{
		int t = stk[top];
		nd.push_back(t / D);
		ins[t] = 0; in[t] = cntSCC;
		if(!mrk[t / D] && open[t]){
			mrk[t / D] = 1;
			++sum[cntSCC];
		}
	}while(stk[top--] != x);
	for(auto t : nd) mrk[t] = 0;
}

void tarjan(int x){
	dfn[x] = low[x] = ++ts;
	vis[x] = ins[x] = 1;
	stk[++top] = x;
	for(int i = head[x] ; i ; i = Ed[i].upEd){
		if(!vis[Ed[i].end]) tarjan(Ed[i].end);
		else if(!ins[Ed[i].end]) continue;
		low[x] = min(low[x] , low[Ed[i].end]);
	}
	if(dfn[x] == low[x]) pop(x);
}

int dfs(int x){
	if(ans[x] != -1) return ans[x];
	ans[x] = 0;
	for(auto t : ch[x]) ans[x] = max(ans[x] , dfs(t));
	return ans[x] += sum[x];
}

inline char getc(){
	char c = getchar();
	while(!isdigit(c)) c = getchar();
	return c;
}

int main(){
#ifndef ONLINE_JUDGE
	freopen("in","r",stdin);
	//freopen("out","w",stdout);
#endif
	memset(ans , -1 , sizeof(ans));
	N = read(); M = read(); D = read();
	for(int i = 1 ; i <= M ; ++i){
		int a = read() , b = read();
		for(int j = 0 ; j < D ; ++j)
			addEd(id(a , j) , id(b , (j + 1) % D));
	}
	for(int i = 1 ; i <= N ; ++i)
		for(int j = 0 ; j < D ; ++j)
			open[id(i , j)] = getc() - '0';
	tarjan(0);
	for(int i = 0 ; i <= id(N , (D - 1)) ; ++i)
		if(in[i])
			for(int j = head[i] ; j ; j = Ed[j].upEd)
				if(in[Ed[j].end] && in[i] != in[Ed[j].end])
					ch[in[i]].push_back(in[Ed[j].end]);
	cout << dfs(in[0]) << endl;
	return 0;
}

D. Cooperative Game

先使用Floyd判环法:找两个人,一个人走一步、一个人走两步,不断走直到这两个人在同一个点。假设其中一个人走了(x + t)步,另一个人走了(2x + 2t)步,然后再所有人一起走,至多(3x + 3t)步所有人就会走到一起。而在这之前肯定会有一段时间所有人已经到达同一个点,只是在环上走,所以当所有人第一次相遇的时候就刚好同时到达终点。

考虑步数,有(t+x equiv 2t + 2x mod c),即(x + t equiv 0 mod c),即(x = c - t mod c)。所以前两个人的总步数为(2(c - t mod c) + 2t leq 2t + 2c)步,最后所有人一起走要走(t)步,所以总共要走(3t+2c)步。

#include<bits/stdc++.h>
using namespace std;

int get(){
    int x;
    string s;
    cin >> x;
    for(int i = 1 ; i <= x ; ++i) cin >> s;
    return x;
}

int main(){
    do{
        puts("next 0 1");
        get();
        puts("next 1");
    }while(get() > 2);
    do{
        puts("next 0 1 2 3 4 5 6 7 8 9");
    }while(get() > 1);
    puts("done");
    return 0;
}

E. Train Car Selection

注意到一次性加一段(0)的操作只有最前面的(0)会产生贡献

对于(1 k)的情况,相当于之前所有的数都不可能会产生贡献

对于其他情况,记录当前所有一次函数的和,将((i,a_i))看做平面上的一个点,那么会产生贡献的点只会出现在所有点的右上凸包上,使用单调栈维护。

#include<iostream>
#include<cstdio>
#include<queue>
#include<set>
//This code is written by Itst
using namespace std;

#define int long long
inline int read(){
	int a = 0;
	char c = getchar();
	while(!isdigit(c))
		c = getchar();
	while(isdigit(c)){
		a = a * 10 + c - 48;
		c = getchar();
	}
	return a;
}

const int MAXN = 3e5 + 7;
#define ld long double
#define PII pair < int , int >
#define st first
#define nd second
PII st[MAXN];
int K , B , top , all;
ld getK(PII a , PII b){return (b.nd - a.nd) * 1.0 / (a.st - b.st);}
int calc(PII nd){return (nd.st - 1) * K + nd.nd + B;}

signed main(){
#ifndef ONLINE_JUDGE
	freopen("in","r",stdin);
	//freopen("out","w",stdout);
#endif
	all = read(); st[top = 1] = PII(1 , 0);
	for(int M = read() ; M ; --M){
		int tp = read();
		if(tp == 1){st[top = 1] = PII(1 , 0); B = K = 0; all += read();}
		else
			if(tp == 2){
				PII now = PII(all + 1 , -calc(PII(all + 1 , 0)));
				while(top > 1 && getK(now , st[top]) >= getK(st[top] , st[top - 1]))
					--top;
				st[++top] = now;
				all += read();
			}
			else{B += read(); K += read();}
		while(top > 1 && calc(st[top]) >= calc(st[top - 1])) --top;
		cout << st[top].st << ' ' << calc(st[top]) << endl;
	}
	return 0;
}

F. Matches Are Not a Child's Play

考虑一次(up)对原树的影响:对于原树中优先级最大的点(u)和当前(up)的点(v),以(u,v)为端点的这一条链一定会在最后被烧掉,而且一定是从(u)(v)依次烧掉。而对于其他的部分,烧掉的顺序和之前的顺序是一样的。

所以考虑:每一次将树根(u)设为当前优先级最大的点,每一次(up v)操作时,给(u)(v)的这一条链的颜色变为当前最大的优先级(+1),然后将(v)变为树根。注意到这两个操作都可以使用LCT完美解决。

最后考虑答案:一个compare操作显然是可以拆成两个when操作的,所以只需要考虑when操作。而when操作的答案就是:当前所有节点中颜色值比当前节点小的点的数量加上在当前点之前被烧掉的与它颜色相同的点的数量。前者可以使用树状数组维护颜色的前缀和;而在当前点之前被烧掉的与它同一个颜色的点就是将当前节点Splay成根节点之后右子树的大小。

#include<iostream>
#include<cstdio>
#include<queue>
//This code is written by Itst
using namespace std;

inline int read(){
	int a = 0;
	char c = getchar();
	while(!isdigit(c))
		c = getchar();
	while(isdigit(c)){
		a = a * 10 + c - 48;
		c = getchar();
	}
	return a;
}

const int MAXN = 2e5 + 3;
int N , Q , cntCol;

namespace BIT{
	int BIT[MAXN << 1];

#define lowbit(x) ((x) & -(x))
	void add(int x , int num){
		while(x <= N + Q){
			BIT[x] += num;
			x += lowbit(x);
		}
	}

	int get(int x){
		int sum = 0;
		while(x){
			sum += BIT[x];
			x -= lowbit(x);
		}
		return sum;
	}
}
using BIT::add; using BIT::get;

namespace LCT{
	struct node{
		int ch[2] , fa , col , sz;
		bool mrk;
	}Tree[MAXN];
	
	bool nroot(int x){
		return Tree[Tree[x].fa].ch[0] == x || Tree[Tree[x].fa].ch[1] == x;
	}

	bool son(int x){
		return Tree[Tree[x].fa].ch[1] == x;
	}

	void pushup(int x){
		Tree[x].sz = Tree[Tree[x].ch[0]].sz + Tree[Tree[x].ch[1]].sz + 1;
	}
	
	void rot(int x){
		bool f = son(x);
		int y = Tree[x].fa , z = Tree[y].fa , w = Tree[x].ch[f ^ 1];
		Tree[x].fa = z;
		if(nroot(y)) Tree[z].ch[son(y)] = x;
		Tree[y].fa = x; Tree[x].ch[f ^ 1] = y;
		Tree[y].ch[f] = w; if(w) Tree[w].fa = y;
		pushup(y);
	}

	void mark(int x , bool f , int col){
		if(!x) return;
		Tree[x].col = col;
		if(f){
			swap(Tree[x].ch[0] , Tree[x].ch[1]);
			Tree[x].mrk ^= 1;
		}
	}
	
	void pushdown(int x){
		mark(Tree[x].ch[0] , Tree[x].mrk , Tree[x].col);
		mark(Tree[x].ch[1] , Tree[x].mrk , Tree[x].col);
		Tree[x].mrk = 0;
	}

	void down_all(int x){
		if(nroot(x)) down_all(Tree[x].fa);
		pushdown(x);
	}
	
	void splay(int x){
		down_all(x);
		while(nroot(x)){
			if(nroot(Tree[x].fa))
				rot(son(x) == son(Tree[x].fa) ? Tree[x].fa : x);
			rot(x);
		}
		pushup(x);
	}
	
	void access(int x){
		for(int y = 0 ; x ; y = x , x = Tree[x].fa){
			splay(x);
			add(Tree[x].col , -(Tree[Tree[x].ch[0]].sz + 1));
			Tree[x].ch[1] = y;
			pushup(x);
		}
	}
	
	void makeroot(int x){
		access(x); splay(x);
		mark(x , 1 , ++cntCol); add(cntCol , Tree[x].sz);
	}

	int query(int x){
		splay(x);
		return get(Tree[x].col - 1) + Tree[Tree[x].ch[1]].sz + 1;
	}
}
using LCT::makeroot; using LCT::query;

namespace Tree{
	struct Edge{
		int end , upEd;
	}Ed[MAXN << 1];
	int head[MAXN] , in[MAXN] , cntEd;

	inline void addEd(int a , int b){
		Ed[++cntEd].end = b;
		Ed[cntEd].upEd = head[a];
		head[a] = cntEd;
		++in[a];
	}

	void init(){
		for(int i = 1 ; i < N ; ++i){
			int a = read() , b = read();
			addEd(a , b); addEd(b , a);
		}
		priority_queue < int , vector < int > , greater < int > > q;
		for(int i = 1 ; i <= N ; ++i)
			if(in[i] == 1) q.push(i);
		while(!q.empty()){
			int t = q.top(); q.pop();
			add(LCT::Tree[t].col = ++cntCol , 1);
			for(int i = head[t] ; i ; i = Ed[i].upEd){
				if(--in[Ed[i].end] == 1)
					q.push(Ed[i].end);
				if(in[Ed[i].end] >= 1 || Ed[i].end == N)
					LCT::Tree[t].fa = Ed[i].end;
			}
		}
	}
}

inline char getc(){
	char c = getchar();
	while(!islower(c)) c = getchar();
	return c;
}
	
int main(){
#ifndef ONLINE_JUDGE
	freopen("in","r",stdin);
	freopen("out","w",stdout);
#endif
	N = read(); Q = read(); Tree::init();
	int a , b;
	for(int i = 1 ; i <= Q ; ++i)
		switch(getc()){
		case 'u': makeroot(read()); break;
		case 'w': printf("%d
" , query(read())); break;
		case 'c':
			a = read(); b = read();
			printf("%d
" , query(a) > query(b) ? b : a);
			break;
		}
	return 0;
}
原文地址:https://www.cnblogs.com/Itst/p/10513777.html