[Contest on 不記得了] 究竟是評測姬快還是數據水?


[弱省胡策] \(\sf OVOO\)

題目描述

\(\sf Oxide\) 有一棵 \(n\) 個點的樹,每條邊有一個權值。

定義一個連通塊為一個點集與使這些點連通的所有邊(這些點必須連通),定義一個連通塊的權值為這個連通塊的邊權和(如果一個連通塊只包含一個點那么它的權值為 \(0\))。
\(\sf Oxide\) 希望你求出包含 \(1\) 號點的所有連通塊中權值第 \(k\) 小的連通塊的權值。如果不存在則輸出滿足條件最大連通塊的權值。

\(n,k\le 10^5\)

解法

考慮從 \(1\) 號點開始擴展,那么有兩種方法:在邊集覆蓋的點中選權值最小的邊擴展;去掉邊集中一條邊,從剩余邊集覆蓋的點中選權值最小的邊擴展。

具體實現用可持久化左偏樹維護。左偏樹維護可選邊集,根就是當前被選的邊。每次去掉根節點維護的邊,合並左右兒子以實現操作二。去掉根節點后,擴展根節點維護的邊的終點 \(v\) 所連的最小權值邊,然后將 \(v\) 所連的邊加入可選邊集,實現操作一。

至於為什么只擴展 \(v\) 所連的最小權值邊,是因為對於未加入邊集的可選邊,連加入可選邊都無法保證,加入可選邊和可選邊的可選邊這種操作肯定是不優的;對於已經被去掉的根節點,它的可選邊也已經加入集合。

由於合並時不能將被合並兩點信息破壞,所以需要可持久化。時間復雜度 \(\mathcal O(n\log (n\log n)+k\cdot (\log k+\log (n\log n)))\)。算得很粗糙…

代碼

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <queue>
#include <iostream>
using namespace std;
typedef long long ll;

const int maxn=1e5+5;

int n,k,d[maxn*45],w[maxn*45];
int ls[maxn*45],rs[maxn*45];
int rt[maxn],idx,to[maxn*45];
struct node {
	int u; ll w;
	node() {}
	node(int U,ll W):u(U),w(W) {}

	bool operator < (const node &t) const {
		return w>t.w;
	}
};
priority_queue <node> q;

int NewNode(int v,int W) {
	w[++idx]=W,to[idx]=v;
	return idx;
}

void Give(int o,int x) {
	ls[o]=ls[x],rs[o]=rs[x];
	w[o]=w[x],to[o]=to[x];
	d[o]=d[x];
}

int merge(int x,int y) {
	if(!x or !y) 
		return x|y;
	int o=++idx;
	if(w[x]>w[y]) swap(x,y);
	Give(o,x);
	rs[o]=merge(rs[o],y);
	if(d[rs[o]]>d[ls[o]])
		swap(ls[o],rs[o]);
	d[o]=d[rs[o]]+1;
	return o;
}

int main() {
	n=read(9),k=read(9)-1;
	for(int i=2;i<=n;++i) {
		int fa=read(9),w=read(9);
		rt[fa]=merge(rt[fa],NewNode(i,w));
	}
	node t=node(0,0);
	q.push(node(rt[1],w[rt[1]]));
	while(k--) {
		if(q.empty()) break;
		t=q.top(); q.pop();
		int nxt=merge(ls[t.u],rs[t.u]);
		if(nxt)
			q.push(node(nxt,t.w+w[nxt]-w[t.u]));
		nxt=merge(nxt,rt[to[t.u]]);
		if(nxt)
			q.push(node(nxt,t.w+w[nxt]));
	}
	print(t.w,'\n');
	return 0;
}

\(\text{[BZOJ 4212] }\)神牛的養成計划

解法

首先,對於每個 \(\rm dna\) 序列哈希一下前綴與后綴,然后對於每個詢問,\(\mathcal O(n)\) 地枚舉即可。

考慮更優的解法。\(\rm trie\) 樹可以快速求出包含某前綴的字符串個數,但是詢問相當於要求同時具有兩個前綴。可以先將 \(\rm dna\) 序列按字典序排序正向插入 \(\rm trie\) 樹,得到包含某前綴的序列下標區間,然后按排序倒序插入 \(\rm trie\) 樹(可持久化),最后查詢前綴對應下標區間內,有多少個 \(\rm dna\) 序列的后綴為給定后綴即可。時間復雜度是 \(\mathcal O(2\cdot 10^6\cdot 32)\)

代碼

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T> 
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')	
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-');
		write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <iostream>
#include <algorithm>
using namespace std;
typedef pair <int,int> type;

const int maxn=2e6+2;

int n,idx,u_idx,t[2][maxn][26];
short siz[maxn],mx[maxn],mn[maxn];
int rt[2001],ans;
string s[2001];

void ins(int &o,int pre,int id) {
	int p; p=o=++u_idx;
	int len=s[id].length();
	for(int i=len-1;i>=0;--i) {
		int d=s[id][i]-'a';
		for(int k=0;k<26;++k)	
			t[1][p][k]=t[1][pre][k];
		t[1][p][d]=++u_idx;
		p=t[1][p][d],pre=t[1][pre][d];
		siz[p]=siz[pre]+1;
	}
}

void ins(short id) {
	int p=0,len=s[id].length();
	for(int i=0;i<len;++i) {
		int d=s[id][i]-'a';
		if(!t[0][p][d]) 
			t[0][p][d]=++idx,
			mn[idx]=3000;
		p=t[0][p][d];
		mx[p]=max(mx[p],id);
		mn[p]=min(mn[p],id);
	}
}

type ask() {
	int len=s[0].length(),p=0;
	for(int i=0;i<len;++i) {
		int d=s[0][i]-'a';
		if(!t[0][p][d])
			return make_pair(0,0);
		p=t[0][p][d];
	}
	return make_pair(mn[p],mx[p]);
}

int ask(int l,int r) {
	int len=s[0].length(),ret=0;
	for(int i=len-1;i>=0;--i) {
		int d=s[0][i]-'a'; 
		ret=siz[t[1][r][d]]-siz[t[1][l][d]];
		if(!ret)
			return 0;
		r=t[1][r][d],l=t[1][l][d];
	}
	return ret;
}

void Decode() {
	cin>>s[0];
	int len=s[0].length();
	for(int i=0;i<len;++i)
		s[0][i]=(s[0][i]-'a'+ans)%26+'a';
}

int main() {
	n=read(9);
	for(int i=1;i<=n;++i)
		cin>>s[i];
	sort(s+1,s+n+1);
	for(int i=1;i<=n;++i)
		ins(i);
	for(int i=1;i<=n;++i)
		ins(rt[i],rt[i-1],i);
	type ran;
	for(int m=read(9);m;--m) {
		Decode();
		ran=ask();
		Decode();
		if(ran.first==0) {
			puts("0");
			continue;
		}
		print(ans=ask(rt[ran.first-1],rt[ran.second]),'\n');
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM