7.13 Day1 wfj学长的神仙数据结构
7.14 Day2 dp
7.16 Day3 hankpipi(yzh)的图论
7.17 Day4 数学
7.22 Day1 爆零 ljq学长的字符串相关
T1 中位数(median)
题意:给定一个长度为\(n\)的排列,请对于所有满足\(1≤i≤j≤n\)且\(j−i≡0\mod 2\)的数对\((i,j)\),计算排列中由第\(i\)至第\(j\)个元素构成的集合的中位数\(m[i:j]\),并求出这些\(m[i : j]* i* j\)的和。\(n\leq 10^{4}\)
题解:学长的题解是双向链表(没学过)
#include <iostream>
#include <vector>
#include<cstdio>
using namespace std;
typedef long long int64;
int main() {
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
int n; cin >> n;
vector<int> element(n);
for (int i = 0; i < n; i += 1) {
cin >> element[i];
}
vector<pair<int, int>> neighbours(n + 1);
int64 result = 0;
for (int i = 0; i < n; i += 1) {
//如果索引在[i,n]范围内,则该值有效
vector<bool> valid(n + 1, false);
for (int j = i; j < n; j += 1) {
valid[element[j]] = true;
}
valid[n + 1] = true;
int last = 0;
//值的数目>中间值,较低值的数目<=中间值
//mid->中间值
int bigger = (n - i), lower = 0, mid = 0;
// 计算每个值,左边和右边的下一个有效值
for (int j = 1; j <= n; j += 1) {
if (not valid[j]) {
continue;
}
if (bigger > lower) {
mid = j;
bigger -= 1;
lower += 1;
}
// 最后一个元素将是当前元素左边的元素
// 最后一个元素右边的元素将是这个元素
neighbours[j].first = last;
neighbours[last].second = j;
last = j;
}
for (int j = n -1 ; j >= i; j -= 1) {
if (lower == bigger + 1) {
// this can be any formula, whatsoever
result += (int64) (j + 1) * (i + 1) * mid;
}
// 值位于擦除值的左侧和右侧
int left = neighbours[element[j]].first;
int right = neighbours[element[j]].second;
// 从“列表”中删除此列表
neighbours[left].second = right;
neighbours[right].first = left;
// 更新中间值、较大或较小的值。
if (element[j] == mid) {
bigger -= 1;
mid = right;
} else if (element[j] < mid) {
lower -= 1;
} else {
bigger -= 1;
}
// even thou it requires only 1 iteration, the implementation
// seems cleaner this way to put the median on the right spot
// move the median to the left
while (lower > bigger) {
mid = neighbours[mid].first;
bigger += 1;
lower -= 1;
}
// move the median to the right
while (bigger > lower) {
mid = neighbours[mid].second;
bigger -= 1;
lower += 1;
}
}
}
cout << result << '\n';
return 0;
}
然后我自己yy出来了一种写法跑的比标程还快 然而考场上爆int了
复杂度\(O(n^{2})\)
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
int n;
int a[maxn],cnt[maxn];
long long ans;
inline void solve()
{
int l=1,r=0,cntt=0,tmp;
long long mid=0;
for(int tl=1;tl<=n;++tl)
{
for(int tr=tl;tr<=n;tr+=2)
{
while(l<tl)
{
if(a[l]<=mid) --cntt;//左端点显然只会递增
--cnt[a[l++]];
}
while(r<tr)
{
if(a[++r]<=mid) ++cntt;
++cnt[a[r]];
}
while(r>tr)
{
if(a[r]<=mid) --cntt;
--cnt[a[r--]];
}
tmp=((tr-tl)>>1)+1;
if(tmp==1)
{
mid=a[tl];
cntt=1;
ans+=mid*tl*tr;
continue;
}
while(cntt<tmp)
cntt+=cnt[++mid];
while(cntt>dizengtmp)
{
if(cntt-cnt[mid]<tmp) break;
cntt-=cnt[mid--];
}
while(cntt==tmp)
{
if(cnt[mid]!=0) break;//处理mid为零的情况
--mid;
}
ans+=mid*tl*tr;
}
}
}
int main()
{
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
solve();
printf("%lld",ans);
return 0;
}
T2 集合(set)
题意:有一棵\(n\)个节点的树,树根的编号为\(1\),初始时每个节点上有一个空集合。接着执行若干个操作,这些操作都形如:对于以该节点为根的子树中的所有节点,在其对应的集合中增加或删除元素\(x\)。每个节点的操作都必须在其全部祖先节点的全部操作完成后才能进行。对一个已经包含了元素\(x\)的集合执行增加元素\(x\)的操作,或者对一个并不包含元素\(x\)的集合执行删除元素\(x\)不会对该集合产生影响。
问最后每个节点对应集合的大小。
输入格式:输入的第一行包含一个正整数\(n\)。接下来的\(n−1\)行每行两个正整数\(u\),\(v\),表示树中存在一条连接着\(u,v\)两点的边。此后\(n\)行中,第\(i\)行第一个整数为\(k_{i}\),接着是\(k_{i}\)个整数\(x_{j}\) ,表示第\(i\)个节点对应的操作。若\(x_{j}\)为正,则表示增加元素\(x_{j}\),若\(x{j}\)为负,则表示删除元素\(x_{j}\)。保证\(n\leq 10^{5},\forall x_{i},1\leq x_{i}\leq n\),同一节点不会对任意一个元素操作两次以上。
题解:直接dfs,途中维护各节点的集合,回溯时恢复
复杂度\(O(n+n\log n)\)
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
struct Edge
{
int fr,to;
}eg[maxn];
set<int> op[maxn],s;
set<int>::iterator it;
int edgenum,n,head[maxn],ans[maxn];
inline void add(int fr,int to)
{
eg[++edgenum].fr=head[fr];
eg[edgenum].to=to;
head[fr]=edgenum;
}
inline void dfs(int now)
{
int tmp;
for(it=op[now].begin();it!=op[now].end();)
{
tmp=*it;
++it;
if(tmp>0)
{
if(s.count(tmp)) op[now].erase(tmp);
else s.insert(tmp);
}
else
{
if(!s.count(-tmp)) op[now].erase(tmp);
else s.erase(-tmp);
}
}
ans[now]=s.size();
for(int i=head[now];i;i=eg[i].fr)
dfs(eg[i].to);
for(it=op[now].begin();it!=op[now].end();++it)
{
tmp=*it;
if(tmp>0) s.erase(tmp);
else s.insert(-tmp);
}
}
int main()
{
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
scanf("%d",&n);
int u,v,k;
for(int i=1;i<n;++i)
scanf("%d%d",&u,&v),add(u,v);
for(int i=1;i<=n;++i)
{
scanf("%d",&k);
for(int j=1;j<=k;++j)
scanf("%d",&u),op[i].insert(u);
}
dfs(1);
for(int i=1;i<=n;++i)
printf("%d\n",ans[i]);
return 0;
}
T3 星际争霸(craft)
题面:
作为战场的狭窄高地可以认为是一个\(r* c\)的方格阵,每个方格可能是可通过和不可通过两种情况。
marine和zergling总是占据其中的一个可通过方格。两个zergling可以处于同一方格,但任一时刻marine不能和任一未死亡的zergling处于同一方格。
marine 和 zergling 均拥有生命值(HP),marine的初始生命值为\(m\),所有zergling的初始生命值均为\(z\)。
游戏可以抽象为回合制,在每个回合中,marine 首先行动。marine 可以选择沿竖直或水平方向移动一格,或站在原地朝某只 zergling 开枪(由于高地非常狭窄,marine可以击中处于高地任何位置的 zergling。marine 的每一枪会减少目标 zergling 的生命值\(1\)点,当一只 zergling 的生命值降低到\(0\)点或以下时会死亡。marine行动完毕后,所有尚未死亡的zergling会同时行动,如果某只zergling和marine相邻,它会攻击marine,否则它会沿着自己当前位置到 marine 当前位置的最短路前进一格,如果有多条最短路,zergling 会按左、上、右、下的顺序依次尝试行动(例如如果左、上都是最短路,zergling 会向左走)。注意,如果某只zergling没有到marine的路径,则它不会移动.如果两只zergling同时在同一格marine,marine 的生命值只会减少\(1\)点,否则每只zergling的攻击都会使marine的生命值减少\(1\)点。
当某个回合结束时,若zergling全部死亡则认为游戏胜利,若marine生命值降低到\(0\)点或以下,或者游戏进行了\(T\)个回合但仍未胜利则认为游戏失败。你需要判定游戏是否可能取得胜利,如果可能,输出取得胜利需要的最少回合数。
输入格式:第一行为r,c,t。接下来r行每行c个字符,"1"表示不可通过,"M"表示maring的初始位置,"Z"和"z"表示两只zergling的初始位置。最后a,b为marine和zergling的hp。保证\(1\leq m\leq 16\),\(1\leq z\leq 99\),\(1\leq t\leq 50\),\(1\leq r,c\leq 6\)。
题解:爆搜??? 被学长D不会搜索
复杂度\(O(t*m*r^{3}*c^{3})\),可以通过。
#include<bits/stdc++.h>
using namespace std;
char mp[10][10];
int r,c,T,hp_m,hp_z,m,z1,z2;
int pos[40][40],wei[40][40],near[40][40];
int dx[4]={0,-1,0,1},dy[4]={-1,0,1,0};
int dp[55][40][40][40][18];
int go(int now,int d)//mergling的行动
{
int i=(now-1)/c,j=(now-1)%c;
int di=i+dx[d],dj=j+dy[d];
if(di<0||dj<0||di>=r||dj>=c||mp[di][dj]=='1') return -1;
return pos[di][dj];
}
int zgo(int now,int to)zergling的行动
{
if(near[now][to]||!now) return now;
int res=-1,dis=0x3f3f3f3f;
for(int d=0;d<4;++d)
{
int tmp=go(now,d);
if(tmp==-1) continue;
if(wei[tmp][to]<dis) dis=wei[tmp][to],res=tmp;
}
if(dis>100) return now;
return res;
}
inline void cmin(int& a,int b)
{
a=a<b?a:b;
}
int main()
{
freopen("craft.in","r",stdin);
freopen("craft.out","w",stdout);
scanf("%d%d%d",&r,&c,&T);
for(int i=0;i<r;++i)
scanf("%s",mp[i]);
scanf("%d%d",&hp_m,&hp_z);
m=z1=z2=-1;
for(int i=0;i<r;++i)
for(int j=0;j<c;++j)
{
pos[i][j]=i*c+j+1;
if(mp[i][j]=='z'||mp[i][j]=='Z')
{dierwei
if(z1==-1) z1=pos[i][j];
else z2=pos[i][j];
}
else if(mp[i][j]=='M') m=pos[i][j];
}
memset(wei,0x3f,sizeof(wei));
for(int i=0;i<r;++i)//最短路
for(int j=0;j<c;++j)
{
if(mp[i][j]=='1') continue;
if(i&&mp[i-1][j]!='1') wei[pos[i][j]][pos[i-1][j]]=1;
if(i!=r-1&&mp[i+1][j]!='1') wei[pos[i][j]][pos[i+1][j]]=1;
if(j&&mp[i][j-1]!='1') wei[pos[i][j]][pos[i][j-1]]=1;T
if(j!=c-1&&mp[i][j+1]!='1') wei[pos[i][j]][pos[i][j+1]]=1;
wei[pos[i][j]][pos[i][j]]=0;
}
int size=r*c;
for(int i=1;i<=size;++i)//判断两格相邻
for(int d=0;d<4;++d)
if(go(i,d)!=-1) near[i][go(i,d)]=1;
for(int k=1;k<=size;++k)
for(int i=1;i<=size;++i)
for(int j=1;j<=size;++j)
cmin(wei[i][j],wei[i][k]+wei[k][j]);
memset(dp,0x3f,sizeof(dp));
dp[0][m][z1][z2][hp_m]=dp[0][m][z2][z1][hp_m]=hp_z<<1;//第一维时间,第二维maring的行动,第三维一只zergling,第四位另一只,第五维是maring剩余的hp
for(int t=0;t<T;++t)
for(int tm=1;tm<=size;++tm)
for(int tz1=0;tz1<=size;++tz1)
for(int tz2=0;tz2<=size;++tz2)
for(int thp=1;thp<=hp_m;++thp)
{
int hp_now=dp[t][tm][tz1][tz2][thp];
if(hp_now>100) continue;
for(int d=0;d<4;++d)
{
int nm=go(tm,d);
if(nm==-1||nm==tz1||nm==tz2) continue;
int v=0;
int nz1=zgo(tz1,nm),nz2=zgo(tz2,nm);
if(near[tz1][nm]) ++v;
if(near[tz2][nm]&&tz1!=tz2&&hp_now>hp_z) ++v;
if(thp>v) cmin(dp[t+1][nm][nz1][nz2][thp-v],hp_now);
}
int nz1=zgo(tz1,tm),nz2=zgo(tz2,tm);
if(hp_now==1)
{
printf("WIN\n%d\n",t+1);
return 0;
}
int v=0;
if(near[tz1][tm]) ++v;
if(near[tz2][tm]&&tz1!=tz2&&hp_now-1>hp_z) ++v;
if(thp>v)
{
if(hp_now-1>hp_z) cmin(dp[t+1][tm][nz1][nz2][thp-v],hp_now-1);
else cmin(dp[t+1][tm][nz1][0][thp-v],hp_now-1);
}
}
puts("LOSE");
return 0;
}
T4 星空(stars)
线段树扫描线模板题(窗口的星星把边界算入)
复杂度\(O(n\log n)\)
#include<bits/stdc++.h>
using namespace std;
int x[100005];
struct Line
{
int l,r,h,val;
friend bool operator < (Line a,Line b)
{
if(a.h==b.h) return a.val>b.val;
return a.h<b.h;
}
}li[100005];
struct SegTree
{
int l,r,val,lazy;
}node[400005];
inline void pushup(int rt)
{
node[rt].val=max(node[rt<<1].val,node[rt<<1|1].val);
}
inline void pushdown(int rt)
{
if(node[rt].lazy)
{
node[rt<<1].lazy+=node[rt].lazy;
node[rt<<1|1].lazy+=node[rt].lazy;
node[rt<<1].val+=node[rt].lazy;
node[rt<<1|1].val+=node[rt].lazy;
node[rt].lazy=0;
}
}
inline void build(int rt,int l,int r)
{
node[rt].l=l,node[rt].r=r;
node[rt].val=node[rt].lazy=0;
if(l==r) return;
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
}
inline void update(int rt,int fr,int to,int l,int r,int val)
{
if(fr<=l&&to>=r)
{
node[rt].val+=val;
node[rt].lazy+=val;
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if(fr<=mid) update(rt<<1,fr,to,l,mid,val);
if(to>mid) update(rt<<1|1,fr,to,mid+1,r,val);
pushup(rt);
}
inline int query(int rt,int fr,int to,int l,int r)
{
if(fr<=l&&to>=r)
return node[rt].val;
pushdown(rt);
int mid=(l+r)>>1,ans=0;
if(fr<=mid) ans=query(rt<<1,fr,to,l,mid);
if(to>mid) ans=max(ans,query(rt<<1|1,fr,to,mid+1,r));
return ans;
}
int cnt;
int main()
{
freopen("stars.in","r",stdin);
freopen("stars.out","w",stdout);
int t,n,W,H,xx,y,z;
//scanf("%d",&t);
//while(t--)
//{
scanf("%d%d%d",&n,&W,&H);
cnt=0;
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&xx,&y,&z);
li[++cnt].l=xx,li[cnt].r=xx+W,li[cnt].h=y,li[cnt].val=z,x[cnt]=xx;
li[++cnt].l=xx,li[cnt].r=xx+W,li[cnt].h=y+H,li[cnt].val=-z,x[cnt]=xx+W;
}
sort(li+1,li+cnt+1);
sort(x+1,x+cnt+1);
build(1,1,cnt);
int maxx=-0x3f3f3f3f,tx=unique(x+1,x+cnt+1)-x-1;
for(int i=1;i<=cnt;++i)
{
li[i].l=lower_bound(x+1,x+tx+1,li[i].l)-x;
li[i].r=lower_bound(x+1,x+tx+1,li[i].r)-x;
update(1,li[i].l,li[i].r,1,cnt,li[i].val);
maxx=max(maxx,node[1].val);
}
printf("%d\n",maxx);
//}
return 0;
}
7.23 Day2 爆零 一些奇奇怪怪的东西好评
T1 点对计数
题意:给定一张\(n\)个点,\(m\)条边的无向图。定义一条从\(i\)点到\(j\)点的路径的代价为路径上所有边的边权最大值。\(i\)到\(j\)的代价定义为i到j所有路径的代价的最小值。
问有多少对\(1≤i<j≤n\)满足\(i\)到\(j\)的代价恰好为\(x\)。保证\(n\leq 10^{5}\),\(m\leq 3* 10^{5}\),\(x\leq 10^{9}\)
题解:转化为小于等于\(x\)减去小于\(x\)的对数。统计的话直接dfs求联通块再\(C_{n}^{2}\)一下就好了
复杂度\(O(n)\)
#include<bits/stdc++.h>
using namespace std;
#define maxn 300005
struct Edge
{
int fr,to,val;
}eg[maxn<<1];
int edgenum,cnt,vis[maxn],head[maxn],fr[maxn],to[maxn],val[maxn];
long long num[maxn];
inline void add(int fr,int to,int val)
{
eg[++edgenum].fr=head[fr];
eg[edgenum].to=to;
eg[edgenum].val=val;
head[fr]=edgenum;
}
inline void dfs(int rt)
{
vis[rt]=1;
for(int i=head[rt];i;i=eg[i].fr)
{
if(vis[eg[i].to]) continue;
vis[eg[i].to]=1;
++num[cnt];
dfs(eg[i].to);
}
}
int main()
{
freopen("xdis.in","r",stdin);
freopen("xdis.out","w",stdout);
int n,m,x;
long long ans=0;
scanf("%d%d%d",&n,&m,&x);
for(int i=1;i<=m;++i)
scanf("%d%d%d",&fr[i],&to[i],&val[i]);
for(int i=1;i<=m;++i)
if(val[i]<=x) add(fr[i],to[i],val[i]),add(to[i],fr[i],val[i]);
for(int i=1;i<=n;++i)
if(!vis[i])
{
++num[++cnt];
dfs(i);
}
for(int i=1;i<=cnt;++i)
ans+=(num[i]-1)*(num[i])/2;
for(int i=1;i<=edgenum;++i)
eg[i].fr=eg[i].to=eg[i].val=0;
cnt=edgenum=0;
memset(head,0,sizeof(head));
memset(num,0,sizeof(num));
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;++i)
if(val[i]<x) add(fr[i],to[i],val[i]),add(to[i],fr[i],val[i]);
for(int i=1;i<=n;++i)
if(!vis[i])
{
++num[++cnt];
dfs(i);
}
for(int i=1;i<=cnt;++i)
ans-=(num[i]-1)*(num[i])/2;
printf("%lld\n",ans);
return 0;
}
T2 树的问题(tree)
题面:有一棵由\(n\)个点构成的无根树,把这\(n\)个点分别作为根节点,我们可以造出\(n\)棵有根树。有\(q\)个询问,每次询问给出两个点\(a\)和\(b\),我们想知道在这\(n\)个有根树中,有多少棵树满足\(a\)是\(b\)的祖先或\(b\)是\(a\)的祖先。保证\(n,q\leq 2* 10^{5}\)。
题解:首先dfs一遍把以1为根的情况下每个节点的size求出来。如果两个节点的lca不是这两个中的任意一个,那么直接size相加(我在考场上只把这个推出来了,其余的推错了)
其他情况下,设\(x,y\)为两个询问的节点且x较深。我们要找到x到y那条链意义下y的儿子,设为z。答案等于\(n-size[z]+size[x]\)。因为在\(z\)上面和\(x\)下面(包括x)的所有点显然符合要求。
复杂度\(O(n\log n)\)
#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
int n,q;
struct Edge
{
int fr,to;
}eg[maxn<<1];
int head[maxn],edgenum;
int top[maxn],size[maxn],son[maxn],id[maxn],fa[maxn],deep[maxn];
inline void add(int fr,int to)
{
eg[++edgenum].fr=head[fr];
eg[edgenum].to=to;
head[fr]=edgenum;
}
inline int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]]) swap(x,y);
x=fa[top[x]];
}
if(deep[x]>deep[y]) swap(x,y);
return x;
}
inline int llca(int x,int y)
{
int last;
if(deep[x]>deep[y]) swap(x,y);
while(top[x]!=top[y]) last=y,y=fa[top[y]];
if(x!=y) return son[x];
return top[last];
}
inline int getans(int x,int y)
{
int tmp=lca(x,y);
if(tmp!=x&&tmp!=y) return size[x]+size[y];
if(tmp==x) return n-size[llca(x,y)]+size[y];
return n-size[llca(x,y)]+size[x];
}
inline void dfs1(int rt,int fat,int dep)
{
size[rt]=1;
fa[rt]=fat;
deep[rt]=dep;
int tmp,maxson=-1;
for(int i=head[rt];i;i=eg[i].fr)
{
tmp=eg[i].to;
if(tmp==fat) continue;
dfs1(tmp,rt,dep+1);
size[rt]+=size[tmp];
if(size[tmp]>maxson)
maxson=size[tmp],son[rt]=tmp;
}
}
int cnt;
inline void dfs2(int rt,int topp)
{mindis(s1,t1)≤l1&&mindis(s2,t2)≤l2
id[rt]=++cnt;
top[rt]=topp;
if(!son[rt]) return;
dfs2(son[rt],topp);
for(int i=head[rt];i;i=eg[i].fr)
if(eg[i].to!=fa[rt]&&eg[i].to!=son[rt])
dfs2(eg[i].to,eg[i].to);
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&n,&q);
int u,v;
for(int i=1;i<n;++i)
{
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
dfs1(1,0,1);
dfs2(1,1);
for(int i=1;i<=q;++i)
{
scanf("%d%d",&u,&v);
printf("%d\n",getans(u,v));
}
return 0;
}
T3 盛会(party)
题意:给定\(n\)点\(m\)边无向连通图,边的边权为1,给定\(s1,t1,l1,s1,t2,l2\),求在满足\(mindis(s1,t1)\leq l1 \& \& mindis(s2,t2)\leq l2\)的条件下最多能断掉多少条边。保证\(n,m\leq 3000\)
题解:转化为最少需要多少条边。若两条最短路径不重复,直接两个长度相加。否则枚举交点\(i,j\),更新最少边数。由于边数极少所以用spfa。(这道题上dijskra比spfa慢了3倍)
复杂度\(O(n^{2})\)
#include<bits/stdc++.h>
using namespace std;
#define maxn 3005
#define mp make_pair
#define pa pair<int,int>
struct Edge
{
int fr,to,val;
}eg[maxn<<1];
int head[maxn],edgenum,n,m,dis[maxn][maxn],vis[maxn];
int s1,t1,l1,s2,t2,l2;
inline void add(int fr,int to)
{
eg[++edgenum].fr=head[fr];
eg[edgenum].to=to;
eg[edgenum].val=1;
head[fr]=edgenum;
}
inline void spfa(int st)
{
queue<int> q;
memset(vis,0,sizeof(vis));
q.push(st);
dis[st][st]=0;
vis[st]=1;
int tmp;
while(!q.empty())
{
tmp=q.front();
q.pop();
vis[tmp]=0;
for(int i=head[tmp];i;i=eg[i].fr)
{
if(dis[st][eg[i].to]>dis[st][tmp]+eg[i].val)
{
dis[st][eg[i].to]=dis[st][tmp]+eg[i].val;
if(!vis[eg[i].to])
{
vis[eg[i].to]=1;
q.push(eg[i].to);
}
}
}
}
}
inline void cmin(int& a,int b)
{
a=min(a,b);
}
int main()
{
freopen("party.in","r",stdin);
freopen("party.out","w",stdout);
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<=m;++i)
{
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
scanf("%d%d%d%d%d%d",&s1,&t1,&l1,&s2,&t2,&l2);
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=n;++i)
spfa(i);
if(dis[s1][t1]>l1||dis[s2][t2]>l2)
{
puts("That's all trouble!");
return 0;
}
int ans=dis[s1][t1]+dis[s2][t2];
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
{
if(dis[s1][i]+dis[i][j]+dis[j][t1]<=l1&&dis[s2][i]+dis[i][j]+dis[j][t2]<=l2)
cmin(ans,dis[s1][i]+dis[s2][i]+dis[i][j]+dis[j][t1]+dis[j][t2]);
if(dis[s1][j]+dis[j][i]+dis[i][t1]<=l1&&dis[s2][i]+dis[i][j]+dis[j][t2]<=l2)
cmin(ans,dis[s1][j]+dis[j][i]+dis[i][t1]+dis[s2][i]+dis[j][t2]);
}
printf("%d\n",m-ans);
return 0;
}
T4 最小生成树(graph)
题意:\(n\)个点,\(m\)个操作,\(Add a b\)表示在\(a,b\)间连一条边权为当前操作编号的边,\(Delete k\)表示将图中边权最大的\(k\)条边删掉,\(Return\)表示将上一次操作撤回。每次操作后求出当前最小生成树的边权和。
最小生成树不存在则输出0。保证第一次不是\(Return\),\(Return\)前面不是\(Return\),当前图中边数大于等于k,\(n\leq 3* 10^{5},m\leq 5* 10^{5}\)。
题解:可持久化lct
直接蒯学长的题解(因为我还不会):专治数据结构学傻了的同学.
部分分与官方题解参见:题解
这道题有比较强的性质:边权是递增的;删除的都是最后加入的边;返回只回到前一个版本.
如果没有这些性质,或许需要可持久化动态树?不过既然性质这么强,解法或许比较简单.
由于边权递增,一条边会一直待在最小生成树中直到被删除.
因为删除的都是边权最大的边,也不必担心某次删边后会有其他边进入最小生成树。
为了支持删边操作,可以使用按秩合并并查集,因为删边自顶而下,所以子树大小也是可以方便地维护的.
唯一麻烦的地方就是撤销操作,可以用一种巧妙的方式解决.
这题没有强制在线,所以每一个操作时都可以知道下一个操作是什么类型.
- 如果是加边然后撤销,可以模拟这一过程,复杂度不受影响.
如果是删边然后撤销,就不必删除这些边,直接用还没有加入这些边时的答案输出(这就意味着要记录每一时刻的答案)
一些细节的地方要注意,比如删除的后\(k\)条边不是后\(k\)条进入了生成树的边.
代码?先咕了
- 7.25 Day3 爆零 lst学姐(大雾)的一些奇奇怪怪的东西 不给大样例差评 连续被Tham D 祭
- 7.26 Day4 爆零 神仙数学 不给大样例差评 神仙题一句话题解差评