正解:最小生成树
解题报告:
然后这题,首先可以发现这神奇的连边方式真是令人头大,,,显然要考虑转化掉QAQ
大概看一下可以发现点对的规律是,左边++,交换位置,再仔细想下,就每个点会连上相邻两点,也就相邻两点会通过另外一个点连边
首先可以发现加到后来已经是麻油意义的了,想下kruscal的意义,当两条边的两端是一样的那显然权值大的那条边麻油意义的,就是说每次最多加n条边
这时候再结合prim,可以发现我们每次加入一个不在联通块的点的时候我们一点也不关心它和哪个点相连的,只要知道和联通块的最短距离多少就好
所以如果有(a,b,c),(b,a+1,c+1),考虑到ab早晚在一个联通块中的,所以可以直接当做是(a,a+1,c+1)
不难想到这样把所有边都处理完之后得到的就是一堆[(a,a+1),w]的边了(这儿这么写的意义是说a和a+1是固定的然而对应了很多w
于是再递推两遍(考虑到环所以是两遍呢QAQ)得到所有(a,a+1)唯一的w,这样就变成了一棵新树,再跑遍最小生成树就好QAQ
然后上面是想法,但是这个想法有一个问题昂,就是它的边还是太多了,所以考虑怎么再优化
可以考虑差分,开个mn[]存当前节点的min,对每个修改就只要修改端点就成
然后最后扫一圈,新的边权就是min(mn[i],mn[i-1]+1)
然后再仔细想下发现,因为它是环,所以要扫两次
然后就做完辣辣辣!
顺便说下,这题非常好地体现了关于最小生成树的两种常见解题策略——去除不可能的边&在不影响答案的情况下改边
#include<bits/stdc++.h> using namespace std; #define il inline #define ll long long #define rg register #define gc getchar() #define rp(i,x,y) for(rg int i=x;i<=y;++i) const int N=200000+10; int n,q,fa[N]; ll mn[N<<1],as; struct ed{int fr,to;ll wei;}; vector<ed>edge; il int read() { rg char ch=gc;rg int x=0;rg bool y=1; while(ch!='-' && (ch>'9' || ch<'0'))ch=gc; if(ch=='-')ch=gc,y=0; while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=gc; return y?x:-x; } il ll readl() { rg char ch=gc;rg ll x=0;rg bool y=1; while(ch!='-' && (ch>'9' || ch<'0'))ch=gc; if(ch=='-')ch=gc,y=0; while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=gc; return y?x:-x; } il bool cmp(ed x,ed y){return x.wei<y.wei;} il int fd(int x){return fa[x]==x?x:fa[x]=fd(fa[x]);} int main() { // freopen("zm.in","r",stdin);freopen("zm.out","w",stdout); n=read();q=read();memset(mn,127/3,sizeof(mn)); while(q--){int a=read()%n+1,b=read()%n+1;ll c=readl();mn[a]=min(mn[a],c+1);mn[b]=min(mn[b],c+2);edge.push_back((ed){a,b,c});} rp(i,1,n<<1)mn[i]=min(mn[i],mn[i-1]+2);rp(i,1,n)edge.push_back((ed){i,i%n+1,min(mn[i],mn[i+n])});sort(edge.begin(),edge.end(),cmp); rp(i,1,n)fa[i]=i;int sz=edge.size();rp(i,0,sz-1){int fafr=fd(edge[i].fr),fato=fd(edge[i].to);if(fafr^fato)as+=edge[i].wei,fa[fafr]=fato;} printf("%lld\n",as); return 0; }