2023.1.11 日寄
一言
\(~~~~\) 过度的希望,自然而然地产生了极度的失望。——博尔赫斯《巴别图书馆》
(名义上的)复习内容:字符串
「JOI Open 2016 T2」 RNA 鎖の販売 / Selling RNA Strands / 销售基因链
Tag
\(~~~~\) Trie、扫描线。
题意
\(~~~~\) \(N\) 个只含
A
,U
,C
,G
的字符串,给出 \(M\) 次查询,每次给出 \(P_i,Q_i\),求多少个字符串以 \(P\) 为前缀且以 \(Q\) 为后缀。
\(~~~~\) \(1\leq \sum |S_i|,|P_i|,|Q_i|\leq 2\times 10^6\).
题解
\(~~~~\) 很自然的想法是对着原来的串和其反串建两棵Trie树,然后每个询问的 \(P\) 和 \(\operatorname{rev}(Q)\) 往这两个Trie树上一走就可以知道每个串满足多少个前缀询问和多少个后缀询问。
\(~~~~\) 注意到两边由于且的条件所以肯定不能单纯取 \(\min\),维护满足的询问来求交也过不去。那自然而然去想让求交变快的方法,比如说区间求交。
\(~~~~\) 那现在我们的思路就很明显了,先把原串按字典序排序后再建Trie树,这样会发现每个前缀询问一定可以被一段连续编号(编号是排序后的顺序)的原串满足,记这个区间为 \([l_1,r_1]\)。同理也对反串这样操作,那我们也可以得到一个区间 \([l_2,r_2]\).
\(~~~~\) 记原来的 \(i\) 串在顺序串排序为 \(id1_i\) 个,逆序串排序为第 \(id2_i\) 个。那所以我们现在的任务是求有多少原串 \(i\) 满足 \(l_1\leq id1_i \leq r_1\) 且 \(l_2\leq id2_i\leq r_2\).但是我们注意到对 \(i\) 没有限制,所以我们不妨就把 \(id2_i\) 依赖在 \(id1_i\) 上。那显然这就成了个二维数点问题,原串 \(i\) 对应点 \((id1_i,id2_i)\) ,每个询问也就是求有多少点在矩形 \((l_1,l_2)-(r_1,r_2)\) 这个矩形内,这就是经典拆矩形离线扫描线问题了。
代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,m,To[300],Ans[4000005];
struct Trie{
int tot,L[4000005],R[4000005],ch[4000005][4];
void Init(){tot=1;memset(L,127,sizeof(L));}
void Insert(string S,int ID)
{
int now=1;
for(int i=0;i<S.length();i++)
{
if(!ch[now][To[S[i]]]) ch[now][To[S[i]]]=++tot;
now=ch[now][To[S[i]]];
L[now]=min(ID,L[now]); R[now]=max(ID,R[now]);
}
// L[now]=min(ID,L[now]); R[now]=max(ID,R[now]);
}
PII Query(string S)
{
int now=1;
for(int i=0;i<S.length();i++)
{
if(!ch[now][To[S[i]]]) return mp(n+1,0);
else now=ch[now][To[S[i]]];
}
return mp(L[now],R[now]);
}
}Trie1,Trie2;
vector <int> P[2000005];
vector <PII> Ask[2000005];
struct BIT{
int tr[2000005];
inline int lowbit(int x){return x&(-x);}
void Add(int x,int Val){for(;x<=n;x+=lowbit(x)) tr[x]+=Val;}
int Query(int x){int ret=0;for(;x;x-=lowbit(x)) ret+=tr[x];return ret;}
}BIT;
vector < pair<string,int> > Str;
inline int Abs(int x){return x>=0?x:-x;}
inline int Sign(int x){return x/Abs(x);}
int main() {
#ifndef ONLINE_JUDGE
freopen("input","r",stdin);
freopen("output","w",stdout);
#endif
To['A']=0; To['C']=1; To['G']=2; To['U']=3;
read(n);read(m); string S;
for(int i=1;i<=n;i++) cin>>S,Str.push_back(mp(S,i));
sort(Str.begin(),Str.end());
Trie1.Init(); Trie2.Init();
for(int i=0;i<n;i++)
{
// cerr<<Str[i].first<<endl;
Str[i].second=i+1;
Trie1.Insert(Str[i].first,i+1);
reverse(Str[i].first.begin(),Str[i].first.end());
}
sort(Str.begin(),Str.end());
for(int i=0;i<n;i++) Trie2.Insert(Str[i].first,i+1),P[Str[i].second].push_back(i+1);//cerr<<Str[i].second<<" "<<i+1<<endl;
string S1,S2;
for(int i=1;i<=m;i++)
{
cin>>S1>>S2;reverse(S2.begin(),S2.end());
PII R1=Trie1.Query(S1),R2=Trie2.Query(S2);
int l1=R1.first,r1=R1.second,l2=R2.first,r2=R2.second;
// cerr<<l1<<" "<<r1<<" "<<l2<<" "<<r2<<endl;
if(l1>r1||l2>r2) continue;
Ask[r1].push_back(mp(r2,i)); Ask[r1].push_back(mp(l2-1,-i));
Ask[l1-1].push_back(mp(r2,-i)); Ask[l1-1].push_back(mp(l2-1,i));
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<P[i].size();j++) BIT.Add(P[i][j],1);
for(int j=0;j<Ask[i].size();j++) Ans[Abs(Ask[i][j].second)]+=Sign(Ask[i][j].second)*BIT.Query(Ask[i][j].first);
}
for(int i=1;i<=m;i++) printf("%d\n",Ans[i]);
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
*/
「HAOI2017」供给侧改革
Tag
\(~~~~\) 随机性质。
题意
不想概括。
题解
\(~~~~\) 直接开始想了SA,胡出依托道理时发现了求和符号,寄!
\(~~~~\) 但是请注意数据随机的性质,这说明什么?原串的任意最大LCP可能很小,所以我们取一个阈值,超过这个值的 \(\operatorname{LCP}\) 我们认为它都不存在。这里我们取 \(40\) 的话, \(\operatorname{LCP}\geq 40\) 概率大概就是 \(10^{-6}\) 数量级,这能出那我还会保底?
\(~~~~\) 然后就是平平无奇的把询问离线下来做,对 \(r\) 从小到大排序,对当前 \(r\) 记录分界点 \(l_i\) 使得 \(\operatorname{data}(l_i,r)=i=\operatorname{data}(l_{i+1},r)+1\) ,那这样我们只用分开计算 \(40\) 个区间的答案即可。
代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
int Len,rec[100005];
char Tmp[45],S[100005];
struct Trie{
int tot,ch[4000005][2],lst[4000005];
void Insert(int ID)
{
int now=1;
for(int i=1;i<=Len;i++)
{
if(!ch[now][Tmp[i]-'0'])
{
ch[now][Tmp[i]-'0']=++tot;
lst[tot]=ID;
}
else
{
rec[i]=max(rec[i],lst[ch[now][Tmp[i]-'0']]);
lst[ch[now][Tmp[i]-'0']]=ID;
}
now=ch[now][Tmp[i]-'0'];
}
}
}Trie;
struct Ask{
int l,r,id;
Ask(){}
Ask(int L,int R){l=L,r=R;}
}A[100005];
int Ans[100005];
bool cmp(Ask x,Ask y){return x.r<y.r||(x.r==y.r&&x.l<y.l);}
int main() {
#ifndef ONLINE_JUDGE
freopen("input","r",stdin);
freopen("output","w",stdout);
#endif
int n,Q;read(n);read(Q);
scanf("%s",S+1); Trie.tot=1;
for(int i=1;i<=Q;i++) read(A[i].l),read(A[i].r),A[i].id=i;
sort(A+1,A+1+Q,cmp);
int now=1;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=i+40-1&&j<=n;j++) Tmp[j-i+1]=S[j];
Len=min(40,n-i+1);
Trie.Insert(i);//记录下了最近的两个
while(now<=Q&&A[now].r==i)
{
int ql=A[now].l;
for(int j=1;j<=40;j++)
{
if(rec[j]>=A[now].l) Ans[A[now].id]+=1ll*j*(rec[j]-max(A[now].l-1,rec[j+1]));
else break;
}
now++;
}
}
for(int i=1;i<=Q;i++) printf("%d\n",Ans[i]);
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
超越一切震慑凡人
SA都是歪门邪道
记录当前加完过后每个LCP的最近那个区间
*/
「CEOI2011」Matching
题意
\(~~~~\) 不想概括 \(\times 2\)
题解
\(~~~~\) 很经典的KMP扩展问题,可以当做套路记下来。
\(~~~~\) 在求相对大小不变的序列的匹配时,只需要满足每个数的模式串对应前驱在文本串中小于自身且对应后继在文本串中大于自身即可判定为 “相等”,这样就直接跑KMP匹配。
代码
查看代码
#include <map>
#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
int b[1000005],p[1000005],pre[1000005],nxt[1000005];
int Last[1000005],Next[1000005];
int fail[1000005],te[1000005];
queue<int> q;
bool check(int P[],int u,int v)
{
return P[v+Last[u]]<=P[v] && P[v+Next[u]]>=P[v];
}
int main() {
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&p[i]);
b[p[i]]=i;
pre[i]=i-1,nxt[i]=i+1;
}
for(int i=1;i<=m;i++) scanf("%d",&te[i]);
for(int i=n;i>=1;i--)
{
int k=b[i];
if(pre[k]>=1) Last[i]=p[pre[k]]-i;
if(nxt[k]<=n) Next[i]=p[nxt[k]]-i;
pre[nxt[k]]=pre[k]; nxt[pre[k]]=nxt[k];
}
int i=2,j=0;
while(i<=n)
{
while(j>0&&!check(b,j+1,i)) j=fail[j];
if(check(b,j+1,i)) j++;
fail[i]=j;
i++;
}
i=1,j=0;
while(i<=m)
{
while(j>0&&!check(te,j+1,i)) j=fail[j];
if(check(te,j+1,i)) j++;
if(j==n) q.push(i-n+1),j=fail[j];
i++;
}
printf("%d\n",(int)q.size());
while(!q.empty()) printf("%d ",q.front()),q.pop();
putchar('\n');
return 0;
}
「BalticOI 2016 Day2」城市
Tag
\(~~~~\) 斯坦纳树。
题意
\(~~~~\) 给定 \(k\) 个关键点,求将其在原带权图连通的最小代价。
\(~~~~\) \(1\leq n\leq 10^5,1\leq k\leq 5\).
题解
\(~~~~\) 是斯坦纳树板子,但是还是记一下,因为算是新的知识。
\(~~~~\) 具体来说状压 DP,记 \(dp_{S,i}\) 表示将关键点 \(S\) 集合连通,生成树的根为 \(i\) 的答案。
\(~~~~\) 那么对当前每个树根你有两种方法:第一种,只选一个传下去,那就是:
\(~~~~\) 条件是 \((i,j)\) 这条边存在。
\(~~~~\) 第二种,裂开成两部分,那就是:
\(~~~~\) 第二种是非常 Simple 的树上背包类似物,\(3^k\) 做即可。
\(~~~~\) 第一种呢?注意到其可以视作 \(S\) 层内的一个松弛操作,并且第二种转移不对它穿绳影响,那不就相当于在某层做一遍松弛吗?那不就直接上Dij吗?
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,ll>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,k,m;
ll dp[33][100005];
vector <PII> G[100005];
struct cmp{
bool operator()(const PII a,const PII b){return a.second>b.second;}
};
priority_queue<PII,vector<PII>,cmp>Q;
void Relax(int S)
{
for(int i=1;i<=n;i++) Q.push(mp(i,dp[S][i]));
while(!Q.empty())
{
// cerr<<"???"<<endl;
int u=Q.top().first;Q.pop();
for(int i=0;i<(int)G[u].size();i++)
{
int v=G[u][i].first;
if(dp[S][v]>dp[S][u]+G[u][i].second) dp[S][v]=dp[S][u]+G[u][i].second,Q.push(mp(v,dp[S][v]));
}
while(!Q.empty()&&Q.top().second>dp[S][Q.top().first]) Q.pop();
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("input","r",stdin);
freopen("output","w",stdout);
#endif
read(n);read(k);read(m);
memset(dp,0x3f,sizeof(dp));
for(int i=1,x;i<=k;i++) read(x),dp[1<<(i-1)][x]=0;
for(int i=1,u,v,w;i<=m;i++)
{
read(u);read(v);read(w);
G[u].push_back(mp(v,w));
G[v].push_back(mp(u,w));
}
for(int S=1;S<(1<<k);S++)
{
for(int A=(S-1)&S;A;A=(A-1)&S)
for(int i=1;i<=n;i++) dp[S][i]=min(dp[A][i]+dp[S-A][i],dp[S][i]);
Relax(S);
}
ll Ans=1e18;
for(int i=1;i<=n;i++) Ans=min(Ans,dp[(1<<k)-1][i]);
printf("%lld",Ans);
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
*/
「ICPC2019 WF」瓷砖
Tag
\(~~~~\) 构造,贪心。
题意
\(~~~~\) 两排瓷砖分别有各自的高度和价格,现在要求把它们排序,使得:
\(~~~~\) · 每排瓷砖价格从左往右递增。
\(~~~~\) · 同一个位置上的瓷砖前一个严格矮于后一个。
\(~~~~\) 求构造方案或报告无解。
\(~~~~\) \(1\leq n\leq 5\times 10^5\).
题解
\(~~~~\) 按价格排序肯定是没跑了,那价格有序过后怎么处理高度呢?
\(~~~~\) 显然关键的问题在于怎么处理相同价格的,我们发现:
\(~~~~\) 对于后排瓷砖,我们尽可能用前排比它矮最少的瓷砖;同理对于前排瓷砖,我们尽可能用后排比它高最少的瓷砖。这种贪心应该是一眼真。
\(~~~~\) 那现在的问题是我们应该用哪个为标准取匹配另外一排的瓷砖呢?答案是用当前处理的价格更少的那个。因为假设有以下情境:后排价格全部一样,有 \(n-1\) 个高为 \(114514\) 和最后一个高为 \(2\) ,前排第一个价格区间有一个 \(1\) ,后面价格区间高度全为 \(114513\)。那如果我们先处理后排,由于当前限死在了第一个区间,这个 \(1\) 就会直接被用掉,那最后那个高为 \(2\) 的就无处安放了。
\(~~~~\) 当然一言以蔽之:让自己的选择范围更宽。
代码
查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
struct Azulejo{
int Pri,High,id;
}A1[500005],A2[500005];
bool cmp(Azulejo a,Azulejo b){return a.Pri<b.Pri;}
set<PII>S1[500005],S2[500005];
int id1[500005],id2[500005];
int main() {
#ifndef ONLINE_JUDGE
freopen("input","r",stdin);
freopen("output","w",stdout);
#endif
int n;read(n);
for(int i=1;i<=n;i++) read(A2[i].Pri);
for(int i=1;i<=n;i++) read(A2[i].High),A2[i].id=i;
for(int i=1;i<=n;i++) read(A1[i].Pri);
for(int i=1;i<=n;i++) read(A1[i].High),A1[i].id=i;
sort(A1+1,A1+1+n,cmp); sort(A2+1,A2+1+n,cmp);
int cnt1=0,cnt2=0;
for(int i=1;i<=n;i++)
{
int j=i; cnt1++;
while(A1[i].Pri==A1[j].Pri) S1[cnt1].insert(mp(A1[j].High,A1[j].id)),j++;
i=j-1;
}
for(int i=1;i<=n;i++)
{
int j=i; cnt2++;
while(A2[i].Pri==A2[j].Pri) S2[cnt2].insert(mp(A2[j].High,A2[j].id)),j++;
i=j-1;
}
int now1=0,now2=0,cnt=0;
while(now1<=cnt1&&now2<=cnt2)
{
if(S1[now1].size()<S2[now2].size())
{
for(auto it=S1[now1].begin();it!=S1[now1].end();it++)
{
cnt++;
id1[cnt]=(*it).second;
auto it2=S2[now2].lower_bound(mp((*it).first+1,0));
if(it2==S2[now2].end()) return puts("impossible")&0;
id2[cnt]=(*it2).second; S2[now2].erase(it2);
}
now1++;
}
else
{
for(auto it=S2[now2].begin();it!=S2[now2].end();it++)
{
cnt++;
id2[cnt]=(*it).second;
auto it1=S1[now1].lower_bound(mp((*it).first,0)); it1--;
if((*it1).first>=(*it).first) return puts("impossible")&0;
id1[cnt]=(*it1).second; S1[now1].erase(it1);
}
now2++;
}
if(S1[now1].empty()) now1++;
if(S2[now2].empty()) now2++;
}
for(int i=1;i<=n;i++) printf("%d ",id2[i]); puts("");
for(int i=1;i<=n;i++) printf("%d ",id1[i]);
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
*/
「Loj #6493」 graph
题意
\(~~~~\) 定义两个点之间的权值为其所有路径经过的最大值的最小值。每个点有颜色,求所有与该点颜色差 \(\leq L\) 的点与之的权值。
\(~~~~\) \(1\leq n\leq 2\times 10^5,1\leq m\leq 4\times 10^5\).
题解
\(~~~~\) 显然这种路径最小值问题丢到克鲁斯卡尔重构树上去跑,这样两个点的路径权值就是其LCA的权值。
\(~~~~\) 那怎么统计答案呢?在LCA处直接统计答案,那就把颜色存下来,显然我们考虑启发式合并,那不就行了吗?
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
vector <int> Inc[200005];
int n,m,L,col[200005],fa[200005],rt[200005],Siz[200005];
struct Edge{
int u,v,w;
Edge(){}
Edge(int U,int V,int W){u=U,v=V,w=W;}
}E[400005];
bool cmp(Edge x,Edge y){return x.w<y.w;}
ll Ans;
inline void makeSet(int N){for(int i=1;i<=N;i++) fa[i]=i,Inc[i].push_back(col[i]),Siz[i]=1;}
inline int findSet(int x){return fa[x]==x?fa[x]:fa[x]=findSet(fa[x]);}
void Union(int x,int y,int z)
{
int rtx=findSet(x),rty=findSet(y);
if(rtx==rty) return;
if(Siz[rtx]>Siz[rty]) swap(rtx,rty);
for(int i=0;i<Inc[rtx].size();i++)
{
int C=Inc[rtx][i];
Ans+=1ll*z*(Siz[rty]-(upper_bound(Inc[rty].begin(),Inc[rty].end(),C+L-1)-lower_bound(Inc[rty].begin(),Inc[rty].end(),C-L+1)));
}
for(int i=0;i<Inc[rtx].size();i++) Inc[rty].insert(lower_bound(Inc[rty].begin(),Inc[rty].end(),Inc[rtx][i]),Inc[rtx][i]);
Siz[rty]+=Siz[rtx]; fa[rtx]=rty;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("input","r",stdin);
freopen("output","w",stdout);
#endif
read(n);read(m);read(L);
for(int i=1;i<=n;i++) read(col[i]);
makeSet(n);
for(int i=1,u,v,w;i<=m;i++)
{
read(u);read(v);read(w);
E[i]=Edge(u,v,w);
}
sort(E+1,E+1+m,cmp);
for(int i=1;i<=m;i++) Union(E[i].u,E[i].v,E[i].w);
printf("%lld",Ans);
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
在克鲁斯卡尔重构树上搞启发式合并,这样合并上来的权值双指针处理?
*/
鲜花
\(~~~~\) 虽然自认为whk几天跟着雷宝坐嘴变贱了。 (都是雷宝把我带坏的我不听我不停我不听) 结果发现更多人nmd嘴比我贱到不知道哪里去了。(
\(~~~~\) 然后就是我觉得晚一晚二上上课还挺休闲的?虽然我这边的东西做不完了(
\(~~~~\) 最后就是,周六有假了!高中牲的快乐就在一瞬间。