九省联考2018

搞学科十分舒适

一双木棋

搜索,在搜索的过程中使用哈希记录重复的状态,每一次枚举合法的放置位置往下递归,取当前所有可行状态中的最优解即可。

#include<bits/stdc++.h>
//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 ll long long
#define PII pair < int , int >
#define st first
#define nd second
unordered_map < ll , PII > Hash;
int st[11] , A[11][11] , B[11][11] , N , M;

ll get(){
	ll hash = 0;
	for(int i = 1 ; i <= M ; ++i)
		hash = hash * 11 + st[i];
	return hash;
}

PII dfs(int cnt){
	if(cnt == N * M)
		return PII(0 , 0);
	ll zt = get();
	if(Hash.find(zt) != Hash.end())
		return Hash[zt];
	PII Max = PII(0 , 0);
	int now = -1e9;
	for(int i = 1 ; i <= M ; ++i)
		if(st[i] < st[i - 1]){
			++st[i]; PII t = dfs(cnt + 1);
			!(cnt & 1) ? t.st += A[st[i]][i] : t.nd += B[st[i]][i];
			int del = !(cnt & 1) ? t.st - t.nd : t.nd - t.st;
			if(now < del){
				now = del;
				Max = t;
			}
			--st[i];
		}
	return Hash[zt] = Max;
}

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)
			A[i][j] = read();
	for(int i = 1 ; i <= N ; ++i)
		for(int j = 1 ; j <= M ; ++j)
			B[i][j] = read();
	st[0] = N;
	PII t = dfs(0);
	cout << t.st - t.nd;
	return 0;
}

IIIDX

把没有父亲的点的父亲看作(0),这样原来的依赖关系就形成了一棵树。

有一个显然的满足(d)不相同时的贪心:按照bfs序贪心选择每个点的权值。对于点(x)(fa_x)中有一些权值可以使用,它一定会选择这些权值中第(sz_x)大的权值作为自己的权值,并使这些权值中的第(1 sim sz_x-1)大的权值作为它的子树中所有点的权值。

但是在有权值相同时这样的贪心就不一定正确了,因为有可能(sz_x+1)大的元素与(sz_x)大的元素相等,也就是说有可能取其中(1 sim sz_x-1)大不一定更优。

我们考虑每一个点,设(f_x)表示对于之前的所有点来说,至少有多少个数(geq x)才能够在之后的选择中存在方案,又设(p_x)表示序列(d)的所有数中(geq x)的数的个数。

那么如果当前点(q)能够选择(x)作为自身权值,那么一定要满足(forall y leq x , f_y + sz_q leq p_y),即(p_y - f_y geq sz_q)。在线段树上维护(p_y - f_y)的最小值就可以二分出(x)的最大值。然后在线段树上对(forall i in [1,x] , f_i += sz_y)表示当前点(y)占用了(sz_y)(geq x)的数。不难证明上述条件是充要的。

注意如果现在考虑到了点(p),那么我们需要去掉(fa_p)的子树对(f)数组的影响,因为我们现在正在考虑(fa_p)的子树,所以(fa_p)的子树之前占用的部分就可以释放出来留给子树进行选择了。

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

#define mid ((l + r) >> 1)
#define lch (x << 1)
#define rch (x << 1 | 1)
const int _ = 5e5 + 3;
int fa[_] , sz[_] , val[_] , tmp[_] , ans[_] , N; double K; bool fr[_];
namespace segt{
	int mrk[_ << 2] , mn[_ << 2];

	void mark(int x , int val){mrk[x] += val; mn[x] += val;}
	void down(int x){mark(lch , mrk[x]); mark(rch , mrk[x]); mrk[x] = 0;}
	void up(int x){mn[x] = min(mn[lch] , mn[rch]);}
	
	void modify(int x , int l , int r , int L , int R , int num){
		if(l >= L && r <= R){mn[x] += num; mrk[x] += num; return;}
		down(x);
		if(mid >= L) modify(lch , l , mid , L , R , num);
		if(mid < R) modify(rch , mid + 1 , r , L , R , num);
		up(x);
	}

	int qry(int x , int l , int r , int val){
		if(l == r) return l - (mn[x] >= val);
		down(x);
		return mn[rch] >= val ? qry(lch , l , mid , val) : qry(rch , mid + 1 , r , val);
	}
}

int main(){
	ios::sync_with_stdio(0); cin >> N >> K;
	for(int i = 1 ; i <= N ; ++i) cin >> val[i];
	sort(val + 1 , val + N + 1 , [&](int a , int b){return a > b;});
	memcpy(tmp , val , sizeof(tmp)); int cnt = unique(val + 1 , val + N + 1) - val - 1;
	for(int i = 1 ; i <= N ; ++i)
		segt::modify(1 , 1 , cnt , lower_bound(val + 1 , val + cnt + 1 , tmp[i] , [&](int a , int b){return a > b;}) - val , cnt , 1);
	for(int i = N ; i ; --i) sz[fa[i] = i / K] += ++sz[i];
	fr[0] = 1;
	for(int i = 1 ; i <= N ; ++i){
		if(!fr[fa[i]]){
			fr[fa[i]] = 1;
			segt::modify(1 , 1 , cnt , ans[fa[i]] , cnt , sz[fa[i]] - 1);
		}
		ans[i] = segt::qry(1 , 1 , cnt , sz[i]) + 1;
		segt::modify(1 , 1 , cnt , ans[i] , cnt , -sz[i]);
	}
	for(int i = 1 ; i <= N ; ++i) printf("%d " ,  val[ans[i]]);
	return 0;
}

秘密袭击

题意相当于(sumlimits_S kth of S),其中(S)是个连通块。考虑计算(f_x=sumlimits_S [kth of S geq x]),答案就是(sumlimits_{i=1}^W i(f_i - f_{i+1}))

枚举(x),注意到(f_x = sumlimits_S [(sumlimits_{p in S} [d_p geq x]) geq k]),故设(val_p = [d_p geq x]),那么就是需要求连通块权值大于等于(k)的连通块个数。

考虑树形背包,设(f_{i,j})表示选择了一个以点(i)为深度最浅的点的连通块,连通块点权之和为(j)的方案数。根据经典的背包套路每一次可以做到(O(nk))

这样程序的复杂度就是(O(nk(n-k))),看起来可以过实际上也可以过,比正解好写到不知道哪里去了

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

#define int long long
const int _ = 2003 , MOD = 64123;
int dp[_][_] , val[_] , lsh[_] , sz[_] , N , K , W;
vector < int > ch[_];

void dfs(int x , int p , int c){
	memset(dp[x] , 0 , sizeof(int) * (K + 1));
	dp[x][sz[x] = val[x] >= c] = 1;
	for(auto t : ch[x])
		if(t != p){
			dfs(t , x , c);
			for(int i = min(sz[x] , K) ; i >= 0 ; --i){
				int p = dp[x][i];
				for(int j = min(sz[t] , K) ; j >= 0 ; --j)
					dp[x][min(i + j , K)] = (dp[x][min(i + j , K)] + p * dp[t][j]) % MOD;
			}
			sz[x] += sz[t];
		}
}

signed main(){
	cin >> N >> K >> W; for(int i = 1 ; i <= N ; ++i) cin >> val[i];
	for(int i = 1 ; i < N ; ++i){
		int a , b; cin >> a >> b;
		ch[a].push_back(b); ch[b].push_back(a);
	}
	for(int i = 1 ; i <= N ; ++i) lsh[i] = val[i];
	sort(lsh + 1 , lsh + N + 1); int pre = 0 , ans = 0;
	for(int i = N - K + 1 ; i ; --i)
		if(i == N - K + 1 || lsh[i] != lsh[i + 1]){
			dfs(1 , 0 , lsh[i]); int sum = 0;
			for(int j = 1 ; j <= N ; ++j) sum = (sum + dp[j][K]) % MOD;
			ans = (ans + lsh[i] * (sum - pre + MOD)) % MOD;
			pre = sum;
		}
	cout << ans;
	return 0;
}

劈配

注意到这是一个匹配问题。

对于第一问,我们枚举每一个人、枚举他实际的录取志愿,考虑是否存在一种方案。这是一个匹配问题,使用网络瘤求解。对所有人将他们向其对应录取志愿的所有导师连(1)边,源点向每个人连(1)边,每个导师向汇点连最大人数边,如果最大瘤量等于在当前考虑的人前面的所有人中能够获得导师的人的数量+1则当前方案可行。

当然枚举有些慢,可以使用二分计算出实际的录取志愿。

对于第二问,使用和第一问相似的思路。二分位置,把在这个位置之前的所有人和当前这个人对应的边放到网络里,如果最大瘤量等于在当前位置之前的所有人中能够获得导师的人的数量+1则当前位置可行。

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

int read(){int a; cin >> a; return a;}

namespace flow{
	const int __ = 1e6 + 7 , _ = 1e5 + 7;
	struct Edge{int end , upEd , f;}Ed[__];
	int cur[_] , head[_] , dep[_] , cntEd , S , T;
	queue < int > q;

	void addEd(int a , int b , int c){Ed[++cntEd] = (Edge){b , head[a] , c}; head[a] = cntEd;}
	void addE(int a , int b , int c){addEd(a , b , c); addEd(b , a , 0);}
	
	bool bfs(){
		while(!q.empty()) q.pop(); q.push(S);
		memset(dep , 0 , sizeof(int) * (T + 1)); dep[S] = 1;
		while(!q.empty()){
			int t = q.front(); q.pop();
			for(int i = head[t] ; i ; i = Ed[i].upEd)
				if(!dep[Ed[i].end] && Ed[i].f){
					dep[Ed[i].end] = dep[t] + 1; q.push(Ed[i].end);
					if(Ed[i].end == T){
						memcpy(cur , head , sizeof(int) * (T + 1));
						return 1;
					}
				}
		}
		return 0;
	}
	
	int dfs(int x , int mn){
		if(x == T) return mn;
		int sum = 0;
		for(int &i = cur[x] ; i ; i = Ed[i].upEd)
			if(Ed[i].f && dep[Ed[i].end] == dep[x] + 1){
				int t = dfs(Ed[i].end , min(Ed[i].f , mn - sum));
				sum += t; Ed[i].f -= t; Ed[i ^ 1].f += t;
				if(sum == mn) break;
			}
		return sum;
	}

	int Dinic(int s , int t){S = s; T = t; int sum = 0; while(bfs()) sum += dfs(S , 1e9); return sum;}
	void clear(){memset(head , 0 , sizeof(int) * (T + 1)); cntEd = 1;}
}using flow::addE;

int T , N , M , B[203] , to[203] , ans[203]; vector < int > need[203][203];

int main(){
	for(T = read() , read() ; T ; --T){
		N = read(); M = read();
		for(int i = 1 ; i <= M ; ++i) B[i] = read();
		for(int i = 1 ; i <= N ; ++i)
			for(int j = 1 ; j <= M + 1 ; ++j)
				need[i][j].clear();
		for(int i = 1 ; i <= N ; ++i)
			for(int j = 1 ; j <= M ; ++j)
				need[i][read()].push_back(j);
		for(int i = 1 ; i <= N ; ++i) to[i] = read();
		for(int i = 1 ; i <= N ; ++i){
			int L = 1 , R = M + 1;
			while(L < R){
				int mid = (L + R) >> 1 , cnt = 1; flow::clear();
				for(int j = 1 ; j < i ; ++j)
					if(ans[j] != M + 1){
						addE(0 , j , 1); ++cnt;
					</details>	for(int k = 0 ; k < need[j][ans[j]].size() ; ++k)
							addE(j , need[j][ans[j]][k] + N , 1);
					}
				addE(0 , i , 1);
				for(int j = 1 ; j <= mid ; ++j)
					for(int k = 0 ; k < need[i][j].size() ; ++k)
						addE(i , need[i][j][k] + N , 1);
				for(int j = 1 ; j <= M ; ++j) addE(j + N , N + M + 1 , B[j]);
				flow::Dinic(0 , N + M + 1) == cnt ? R = mid : L = mid + 1;
			}
			printf("%d%c" , ans[i] = L , " 
"[i == N]);
		}
		for(int i = 1 ; i <= N ; ++i)
			if(ans[i] <= to[i]) printf("0%c" , " 
"[i == N]);
			else{
				int L = 0 , R = i - 1;
				while(L < R){
					int mid = (L + R + 1) >> 1 , cnt = 1; flow::clear();
					for(int j = 1 ; j < mid ; ++j)
						if(ans[j] != M + 1){
							addE(0 , j , 1); ++cnt;
							for(int k = 0 ; k < need[j][ans[j]].size() ; ++k)
								addE(j , need[j][ans[j]][k] + N , 1);
						}
					addE(0 , i , 1);
					for(int j = 1 ; j <= to[i] ; ++j)
						for(int k = 0 ; k < need[i][j].size() ; ++k)
							addE(i , need[i][j][k] + N , 1);
					for(int j = 1 ; j <= M ; ++j) addE(j + N , N + M + 1 , B[j]);
					flow::Dinic(0 , N + M + 1) == cnt ? L = mid : R = mid - 1;
				}
				printf("%d%c" , i - L , " 
"[i == N]);
			}
	}
	return 0;
}

林克卡特树

题目等价于选出(k+1)条点不重复的路径使得边权和最大。

对于(k leq 100)的部分考虑树形背包:设(f_{i,j,0/1/2})表示点(i)的子树内有(j)条路径,点(i)向下与(0/1/2)个儿子有连边的最大权值和。转移枚举儿子考虑是否连边。有三个细节:可能存在(f_{i,j,0})作为一条链起始的情况;两条链合并在一起的时候路径数量会(-1)(1)号点可能单独作为一条路径。

然后打表可以发现设(g_j)表示(k=j)时的答案,那么(g_j)是一个凸函数,即(g_j - g_{j-1} geq g_{j+1} - g_j),也就是差分之后数组单调递减。

设差分之后的数组为(p_x = g_x - g_{x-1}),我们考虑二分(p_{k+1})的值。设当前二分到(mid),考虑给所有(p_x -= mid(x geq 1)),如果(mid = p_{k+1}),则新的(g_k)(g_{k+1})都会是数列极值。而当(mid)增大的时候,新的数列的极值点在变小,满足单调性。所以二分可行。

对于所有(p_x -= mid)相当于给(g_x -= x imes mid),在原问题中相当于选择一条路径产生(mid)的代价。这个部分在(k leq 100)的背包中可简单进行贡献。而二分后背包部分不会受到路径条数限制,所以背包(j)维可以删掉,只需要在背包的每个状态记录到达当前最大贡献的路径条数即可。

这样总复杂度就是(O(nlog(nw))),可以通过。

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

int read(){int a; scanf("%d" , &a); return a;}

#define PII pair < long long , int >
const int _ = 3e5 + 7;
PII dp[_][3]; int N , K , val; vector < PII > ch[_];

PII max(PII A , PII B){return A.first > B.first ? A : (A.first == B.first && A.second < B.second ? A : B);}
PII operator +(PII A , PII B){return PII(A.first + B.first , A.second + B.second);}

void dfs(int x , int p){
    dp[x][0] = PII(0 , 0); dp[x][1] = PII(-1e18 , 0); dp[x][2] = PII(val , 1);
    for(auto t : ch[x])
        if(t.first != p){
            dfs(t.first , x);
            PII v2 = max(max(dp[t.first][0] , dp[t.first][1]) , dp[t.first][2]);
            PII v = max(dp[t.first][1] , dp[t.first][0] + PII(val , 1));
            dp[x][2] = max(dp[x][2] + v2 , dp[x][1] + v + PII(t.second - val , -1));
            dp[x][1] = max(dp[x][1] + v2 , dp[x][0] + v + PII(t.second , 0));
            dp[x][0] = dp[x][0] + v2;
        }
}

int main(){
	freopen("lct.in","r",stdin); freopen("lct.out","w",stdout);
    N = read(); K = read();
    for(int i = 1 ; i < N ; ++i){
        int a = read() , b = read() , c = read();
        ch[a].emplace_back(b , c); ch[b].emplace_back(a , c);
    }
    long long L = -1e12 , R = 1e12;
    while(L < R){
        int mid = (L + R) >> 1; val = -mid; dfs(1 , 0);
        max(max(dp[1][0] , dp[1][1]) , max(dp[1][2] , dp[1][0] + PII(val , 1))).second < K + 1 ? R = mid : L = mid + 1;
    }
    val = -L; dfs(1 , 0); cout << max(max(dp[1][0] , dp[1][1]) , max(dp[1][2] , dp[1][0] + PII(val , 1))).first + (K + 1) * L;
    return 0;
}

制胡窜

已经不知道怎么做了所以鸽了

#include<bits/stdc++.h>
//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 int long long
const int _ = 1e5 + 7; int N , Q , ind[_]; char s[_];

struct DATA{int minN , maxN , sum; DATA(){maxN = sum = 0; minN = 1e9;}};

namespace segTr{
	struct node{int l , r; DATA data;}Tr[_ * 80]; int cntN;
	
#define mid ((l + r) >> 1)
#define lch Tr[x].l
#define rch Tr[x].r

	inline DATA pushup(DATA x , DATA y){
		DATA t; t.minN = min(x.minN , y.minN); t.maxN = max(x.maxN , y.maxN);
		t.sum = x.sum + y.sum + (x.maxN && y.minN != 1e9 ? (y.minN - x.maxN) * y.minN : 0);
		return t;
	}
	
	int modify(int x , int l , int r , int tar){
		int t = ++cntN; Tr[t] = Tr[x];
		if(l == r){Tr[t].data.minN = Tr[t].data.maxN = tar; return t;}
		mid >= tar ? Tr[t].l = modify(Tr[t].l , l , mid , tar) : Tr[t].r = modify(Tr[t].r , mid + 1 , r , tar);
		Tr[t].data = pushup(Tr[Tr[t].l].data , Tr[Tr[t].r].data); return t;
	}
	
	int merge(int x , int y){
		if(!x || !y) return x + y;
		int t = ++cntN; Tr[t].l = merge(Tr[x].l , Tr[y].l); Tr[t].r = merge(Tr[x].r , Tr[y].r);
		Tr[t].data = pushup(Tr[Tr[t].l].data , Tr[Tr[t].r].data);
		return t;
	}

	DATA query(int x , int l , int r , int L , int R){
		if(L > R) return DATA();
		if(l >= L && r <= R) return Tr[x].data;
		DATA t;
		if(mid >= L) t = pushup(t , query(lch , l , mid , L , R));
		if(mid < R) t = pushup(t , query(rch , mid + 1 , r , L , R));
		return t;
	}

	int qMax(int x , int l , int r , int tar){
		if(l == r) return Tr[x].data.maxN;
		if(mid >= tar) return qMax(Tr[x].l , l , mid , tar);
		return max(Tr[Tr[x].l].data.maxN , qMax(Tr[x].r , mid + 1 , r , tar));
	}

	int qMin(int x , int l , int r , int tar){
		if(l == r) return Tr[x].data.minN;
		if(mid >= tar) return min(Tr[Tr[x].r].data.minN , qMin(Tr[x].l , l , mid , tar));
		return qMin(Tr[x].r , mid + 1 , r , tar);
	}
}
using segTr::modify; using segTr::qMax; using segTr::qMin; using segTr::merge;

namespace SAM{
	int trans[_ << 1][10] , fa[_ << 1] , Len[_ << 1] , endpos[_ << 1];
	int cnt = 1 , lst = 1 , rt[_ << 1] , jump[_ << 1][20];
	vector < int > ch[_ << 1];

	int insert(int l , int x){
		int t = ++cnt , p = lst; lst = t; endpos[t] = Len[t] = l;
		while(p && !trans[p][x]){trans[p][x] = t; p = fa[p];}
		if(!p){fa[t] = 1; return t;}
		int q = trans[p][x]; if(Len[q] == Len[p] + 1){fa[t] = q; return t;}
		int k = ++cnt; Len[k] = Len[p] + 1;
		memcpy(trans[k] , trans[q] , sizeof(trans[q]));
		fa[k] = fa[q]; fa[q] = fa[t] = k;
		while(trans[p][x] == q){trans[p][x] = k; p = fa[p];}
		return t;
	}
	
	void dfs(int x , int p){
		jump[x][0] = p; if(endpos[x]) rt[x] = modify(rt[x] , 1 , N , endpos[x]);
		for(int i = 1 ; jump[x][i - 1] ; ++i) jump[x][i] = jump[jump[x][i - 1]][i - 1];
		for(auto t : ch[x]){dfs(t , x); rt[x] = merge(rt[x] , rt[t]);}
	}
	
	void init(){
		for(int i = 2 ; i <= cnt ; ++i) ch[fa[i]].push_back(i);
		dfs(1 , 0);
	}

	int Jump(int x , int len){
		for(int i = 18 ; i >= 0 ; --i) if(Len[jump[x][i]] >= len) x = jump[x][i];
		return x;
	}

#define calc2(x) ((x) * (x - 1) / 2)
	int query(int x , int len){
		if(len == 1) return calc2(N - 1);
		DATA p = segTr::Tr[rt[x]].data;
		int l1 = p.minN - len + 1 , r1 = p.minN , lm = p.maxN - len + 1 , rm = p.maxN;
		if(lm < r1) return calc2(N - 1) - (l1 - 1) * (r1 - lm) - (p.sum - (rm - r1) * lm) - (calc2(N - lm) - calc2(N - r1));
		int mL = qMax(rt[x] , 1 , N , r1 + len - 2) , mR = qMin(rt[x] , 1 , N , lm + 1);
		if(mL < mR)
			if(segTr::query(rt[x] , 1 , N , mL + 1 , mR - 1).maxN) return calc2(N - 1);
			else return calc2(N - 1) - (r1 - (mL - len + 1)) * (mR - lm);
		DATA q = segTr::query(rt[x] , 1 , N , mR , mL); mL = qMin(rt[x] , 1 , N , mL + 1); mR = qMax(rt[x] , 1 , N , mR - 1);
		return calc2(N - 1) - (q.sum - (q.maxN - q.minN) * lm) - (r1 - (q.maxN - len + 1)) * (mL - lm) - (q.minN - mR) * (q.minN - lm);
	}
}

signed main(){
	freopen("cutting.in","r",stdin); freopen("cutting.out","w",stdout);
	N = read(); Q = read();
	scanf("%s" , s + 1);
	for(int i = 1 ; i <= N ; ++i) ind[i] = SAM::insert(i , s[i] - '0');
	SAM::init();
	while(Q--){
		int l = read() , r = read();
		printf("%lld
" , SAM::query(SAM::Jump(ind[r] , r - l + 1) , r - l + 1));
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Itst/p/11480757.html