杭电ACM 暑假个人pk赛(3)

7-1 蜂巢

题面

image.png

输入格式:

image.png

image.png

输出格式:

image.png

输入样例1:

2
4 0
4 1
4 2

输出样例1:

5 0
6 0

输入样例2:

1 
0 0 
1 1

输出样例2:

2 -1

image.png

时间限制 1000 ms 内存限制 64 MB

解题思路

 考虑旋转向量AB,我们可以把AB变成AO+OB,我们分别旋转AO与OB,旋转后在加起来就是旋转后的向量AB。其中AO和OB可以取沿着坐标轴方向(x:右,y:右上),方便旋转。考虑旋转对坐标的影响即可。

AC代码

#include <bits/stdc++.h>
using namespace std;
int n,X,Y;
int main(){
	scanf("%d",&n);
	scanf("%d%d",&X,&Y);
	for (int i=1;i<=n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		int dx=x-X,dy=y-Y;
		int xx=X,yy=Y;
		xx+=dx+dy;
		yy+=-dx;
		printf("%d %d\n",xx,yy);
	}
	return 0;
}

7-2 数列

题面

]\_)MQ9})U\`PX[4SLNZ(NPL1.png

输入格式:

第一行包含一个正整数 n。

1RU@HFU8YZ(RJXTD\`TM2Z49.png

输出格式:

image.png

输入样例1:

3

输出样例1:

4

输入样例2:

4

输出样例2:

6

输入样例3:

5

输出样例3:

3

输入样例4:

35

输出样例4:

42

时间限制 1000 ms 内存限制 256 MB

解题思路

考虑若GCD(f[i-1],f[i])第一次包含质因数p,那么f[i]一定等于p,第二次包含质因数p,那结果只可能(也不一定就是,因为会有很多质因数)是2p而不会是3p或者更大(如果都没出现过,总之是最小的k,满足kp没出现过)。所以我们考虑对于每一个质数维护一下出现次数,当算至f[i-1]时,我们把f[i-1]质因数分解,根据每个质因数出现的次数,推出包含质因数pi的最小值,然后对这些最小值取一个min即可。注意同一个数可能会被不同质因数取得,我们打一个表记录下有没有出现过即可。

对小数据打表可以发现f[n]与n基本是一个数量级的,处理出1e6范围的质数,表开2e6即可通过。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+5,N=1e6;
int f[maxn],n,Max;
bool isprime[maxn],vis[maxn<<1];
int prime[100005],now[100005],id[maxn];
inline int gcd(int x,int y){
	return y==0?x:gcd(y,x%y);
}
void Build(){
	memset(isprime,1,sizeof(isprime));
	prime[0]=0,isprime[1]=0;
	for (int i=1;i<=N;i++){
		if (isprime[i])
			prime[++prime[0]]=i,id[i]=prime[0];		
		for (int j=1;j<=prime[0]&&prime[j]*i<=N;j++){
				isprime[prime[j]*i]=0;
				if (i%prime[j]==0)
					break;
			}
	}
	for (int i=1;i<=prime[0];i++)
		now[i]=prime[i];
	now[1]=4;
}
int main(){
	Build();
	scanf("%d",&n);
	vis[2]=1;
	f[1]=1,f[2]=2;
	for (int i=3;i<=n;i++){
		int Min=1e9,x=f[i-1];
		for (int j=1;j<=prime[0]&&prime[j]*prime[j]<=x;j++){
			while (vis[now[j]])
				now[j]+=prime[j];
			if (x%prime[j]==0){
				Min=min(Min,now[j]);
				while (x%prime[j]==0)
					x/=prime[j];
			}
		}
		if (x!=1){
			while (vis[now[id[x]]])
				now[id[x]]+=prime[id[x]];
			Min=min(Min,now[id[x]]);
		}
		f[i]=Min,vis[Min]=1;
	}
	printf("%d\n",f[n]);
	return 0;
}

7-3 体检

题面

一群学生正准备进行体检。体检包含许多项目,依次编号为 1, 2, 3, ...。

学生们现在正排成一条长队,等待着体检的开始。学生们从队首到队尾依次编号为 1, 2, 3, ...。他们 每人都需要做完全部体检项目,且必须按照编号 1, 2, 3, ... 的顺序依次做每项检查。

不同项目之间的检查是同时进行的,但是每个项目同一时间只能检查一个学生。学生之间非常友好, 因此不会出现插队的情况,每个学生在上一个学生体检完这一项后才会开始这一项的体检。

每个学生有一个正整数 hi,用来衡量他们的健康指数。对于学生 i,他做每项检查都需要恰好 hi 分 钟。你可以认为同一项目前后交接、每个学生换到下一项目不花费任何时间。

请写一个程序,计算在时刻 t 每个学生正在等待或者正在检查的项目编号。

输入格式:

第一行包含两个整数 n, t,分别表示学生的数量以及询问的时刻 t。

接下来 n 行,每行一个正整数 hi,依次表示每个学生的健康指数。

F]PKAR%5{\`D_WM0ENPXL1}Q.png

输出格式:

输出 n 行,每行一个正整数。第 i 行输出在整个体检开始 t + 0.5 分钟后,第 i 名学生正在检查或 者正在排队等候的项目编号。你可以认为在该时刻没有任何一个学生完成了整个体检。

输入样例1:

3 20
5
7
3

输出样例1:

5
3
2

输入样例2:

5 1000000000
5553
2186
3472
2605
1790

输出样例2:

180083
180083
180082
180082
180082

时间限制 1000 ms 内存限制 64 MB

解题思路

考虑求出两个东西:第i名学生什么时候开始第1项体检,每次开始体检间隔多大?开始时间直接对hi前缀和即可,考虑怎么求间隔d[i]:

如果第i位学生的体检时间h[i]大于i-1同学的间隔时间d[i-1],那么每次i-1学生体检好了,i仍体检,换句话说i学生每次体检好都能无缝进行下一次,所以的d[i]=h[i]

如果h[i]<d[i-1],那么i同学必须等待i-1同学体检完毕才能体检,而且i-1同学一完成i同学变能接上,所以d[i]=d[i-1]

有了这两个信息,答案就比较好求了。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
typedef long long LL;
int a[maxn],d[maxn];
LL s[maxn];
int n,T;
int main(){
	scanf("%d%d",&n,&T);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for (int i=1;i<=n;i++){
		s[i]=s[i-1]+a[i-1];
		if (a[i]>=d[i-1])
			d[i]=a[i];
		else
			d[i]=d[i-1];
	}
	for (int i=1;i<=n;i++){
		LL t=T-s[i];
		if (t<0){
			printf("1\n");
			continue;
		}
		LL cnt=t/d[i]+1;
		if (t%d[i]>=a[i])
			cnt++;
		printf("%lld\n",cnt);
	}
	return 0;
}

7-4 黑客

题面

Alice, Bob 和 Cathy 是好朋友,他们刚考完一场考试。考卷由 n 道单项选择题构成,每道题分值都 为 1,且每道题都恰有四个选项“A”, “B”, “C”, “D”。抱着不会做也要瞎蒙的原则,他们三人均没有空任何 一道题。

在考试结束后,他们发现自己做错了很多题目。在现在这个科技发达的年代,选择题都是由电脑自 动批卷的。他们决定施展高超的技术水平,黑入教学系统,修改这场考试的标准答案,让他们三人中得 分最低的人得分最高。

比如,假设这场考试一共有 3 题,Alice 的答案是“ABC”,Bob 的答案是“BCD”,Cathy 的答案 是“CDA”。如果他们把标准答案修改为“CCC”的话,那么每个人都能得至少一分。

请写一个程序,帮助他们生成一份标准答案,使得他们三人中得分最低的人得分最高。

输入格式:

第一行包含一个正整数 T,表示测试数据的组数。

每组数据第一行包含一个正整数 n,表示题目的数量。

第二行包含一个长度为 n 的由“A”, “B”, “C”, “D”构成的字符串 a,表示 Alice 的答案。

第三行包含一个长度为 n 的由“A”, “B”, “C”, “D”构成的字符串 b,表示 Bob 的答案。

第四行包含一个长度为 n 的由“A”, “B”, “C”, “D”构成的字符串 c,表示 Cathy 的答案。

]C()Y8ABA201I}L[Z8SD)(6.png

输出格式:

对于每组数据,输出一行一个整数,即修改标准答案之后,三人中得分最低的人的得分。

输入样例:

2
3
ABC
BCD
CDA
4
AABC
AACD
BBAA

输出样例:

1
2

时间限制 1000 ms 内存限制 64 MB

解题思路

看似DP其实是大模拟

对于一道题,只有三种可能:全一样,两个人一样,都不一样。

全一样不用说,都不一样的题可以看作白搭牌,最后用来填补得分最少人的得分。

有两个人一样的题:分成AB型,AC型,BC型三种,先保证三人总得分最大,即让这个题多数人对,这样子肯定会导致分数的不平均,我们在把都不一样的题的分数加给当前得分最少的人(一分分加)。在考虑得分最低的人(比如是C),如果把AB型题目分数转给C,答案更优,那么就这么操作,直到无法操作,得到的便是答案。

考虑正确性:分配全不一样题面我们是遵循最小原则的,改变一定会导致答案变小。若修改AB型以外的题型,会导致C分数变小,答案劣化。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
int cnt[4],n,T,now[3],ans;
char A[maxn],B[maxn],C[maxn];
int min3(){
	int a=0,b=1,c=2;
	if (now[a]<=now[b]&&now[a]<=now[c])
		return a;
	a=1,b=0,c=2;
	if (now[a]<=now[b]&&now[a]<=now[c])
		return a;
	a=2,b=0,c=1;
	if (now[a]<=now[b]&&now[a]<=now[c])
		return a;
	return -1;
}
int max3(){
	int a=0,b=1,c=2;
	if (now[a]>=now[b]&&now[a]>=now[c])
		return a;
	a=1,b=0,c=2;
	if (now[a]>=now[b]&&now[a]>=now[c])
		return a;
	a=2,b=0,c=1;
	if (now[a]>=now[b]&&now[a]>=now[c])
		return a;
	return -1;
}
int main(){
	scanf("%d\n",&T);
	while (T--){
		scanf("%d\n",&n);
		scanf("%s",A+1);
		scanf("%s",B+1);
		scanf("%s",C+1);
		ans=0;
		memset(cnt,0,sizeof(cnt));
		memset(now,0,sizeof(now));
		for (int i=1;i<=n;i++)
			if (A[i]==B[i]&&B[i]==C[i])
				ans++;
			else if (A[i]==B[i])
				cnt[0]++;
			else if (A[i]==C[i])
				cnt[1]++;
			else if (B[i]==C[i])
				cnt[2]++;
			else
				cnt[3]++;
		now[0]=cnt[0]+cnt[1];
		now[1]=cnt[0]+cnt[2];
		now[2]=cnt[1]+cnt[2];
		for (int i=1;i<=cnt[3];i++)
			now[min3()]++;
		while (1){
			int Min=min3();
			if (Min==2){
				if (cnt[0]>0&&min(now[0]-1,now[1]-1)>now[2])
					now[0]--,now[1]--,now[2]++,cnt[0]--;
				else
					break;
			}
			if (Min==1){
				if (cnt[1]>0&&min(now[0]-1,now[2]-1)>now[1])
					now[0]--,now[2]--,now[1]++,cnt[1]--;
				else
					break;
			}
			if (Min==0){
				if (cnt[2]>0&&min(now[1]-1,now[2]-1)>now[0])
					now[1]--,now[2]--,now[0]++,cnt[2]--;
				else
					break;
			}
		}
		printf("%d\n",now[min3()]+ans);
	}
	return 0;
}

7-5 GPS定位

题面

有了GPS定位系统,你可以随时计算当前位置(XA​,YA​)与目的地(XB​,YB​)的曼哈顿距离∣XA​−XB​∣+∣YA​−YB​∣。

假设目的地的坐标是整数,且没有更换过目的地,给定n条GPS定位的记录,请计算目的地的具体坐标。

输入格式:

第一行包含一个正整数n,表示记录的条数。

接下来n行,每行三个整数Xi​,Yi​,Di​,表示(Xi​,Yi​)与目的地的曼哈顿距离为Di​。

输出格式:

若无解,输出impossible;若解不唯一,输出uncertain;否则输出一行两个整数(X,Y),即目的地的坐标。

输入样例:

3
999999 0 1000
999900 950 451
987654 123 13222

输出样例:

1000200 799

111.png

时间限制 400 ms 内存限制 64 MB

解题思路

其实就是求一堆竖着的正方形的交,考虑如何好写一点。

首先旋转45°,方便处理。旋转可以通过对角线分类(x,y)->(x-y,x+y),不用担心距离怎么求,我们可以最后把坐标变回来(x,y)->((x+y)/2,(y-x)/2)再求,减少思维量。

接下来问题就变成了求一堆正方形的交线or交点,这是有点繁琐的,我们可以直接求相交区域,我们能保证如果有答案,答案一定会出现在这个区域边界线上。再具体点,答案一定是出现在这个区域四个端点的附近。

我们直接枚举端点附近的四个点以及端点本身,然后再验证是否是答案即可。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3+5,fl[5][2]={0,0,-1,0,0,1,1,0,0,-1};
int n,dis[maxn];
struct PointData{
	int x,y;
	PointData Trans(){
		return (PointData){x-y,x+y};
	}
	PointData deTrans(){
		return (PointData){(x+y)/2,(y-x)/2};
	}
	bool operator <(const PointData &B){
		return (y<B.y)||(y==B.y&&x<B.x);
	}
	bool operator !=(const PointData &B){
		return x!=B.x||y!=B.y;
	}
} a[maxn],ans;
bool HaveAns=0;
struct RectangleData{
	PointData S,T;
} b[maxn],Area;
void sort(PointData p[]){
	for (int i=0;i<=2;i++)
		for (int j=i+1;j<=3;j++)
			if (p[j]<p[i])
				swap(p[i],p[j]);
}
void Noanswer(){
	printf("impossible\n");
	exit(0);
}
void uncertain(){
	printf("uncertain\n");
	exit(0);
}
RectangleData merge(RectangleData A,RectangleData B){
	int Lx,Rx,Ly,Hy;
	Lx=max(A.S.x,B.S.x);
	Rx=min(A.T.x,B.T.x);
	Ly=max(A.S.y,B.S.y);
	Hy=min(A.T.y,B.T.y);
	if (Lx>Rx||Ly>Hy)
		Noanswer();
	return (RectangleData){(PointData){Lx,Ly},(PointData){Rx,Hy}};
}
int calc(PointData A,PointData B){
	return abs(A.x-B.x)+abs(A.y-B.y);
}
void check(int x,int y){
	for (int k=0;k<=4;k++){
		PointData p=(PointData){x+fl[k][0],y+fl[k][1]}.deTrans();
		bool tag=1;
		for (int i=1;i<=n;i++)
			if (calc(p,a[i])!=dis[i]){
				tag=0;
				break;
			}
		if (tag==1){
			if (!HaveAns)
				HaveAns=1,ans=p;
			else
				if (ans!=p)
					uncertain();
		}
	}
}
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++){
		scanf("%d%d%d",&a[i].x,&a[i].y,&dis[i]);
		PointData p[4];
		p[0]=(PointData){a[i].x-dis[i],a[i].y}.Trans();
		p[1]=(PointData){a[i].x,a[i].y+dis[i]}.Trans();
		p[2]=(PointData){a[i].x+dis[i],a[i].y}.Trans();
		p[3]=(PointData){a[i].x,a[i].y-dis[i]}.Trans();
		sort(p);
//		printf("%d:(%d,%d) (%d,%d) (%d,%d) (%d,%d)\n",
//			i,p[0].x,p[0].y,p[1].x,p[1].y,p[2].x,p[2].y,p[3].x,p[3].y);
		b[i]=(RectangleData){p[0],p[3]};
	}
	Area=b[1];
	for (int i=2;i<=n;i++)
		Area=merge(Area,b[i]);
	check(Area.S.x,Area.S.y);
	check(Area.T.x,Area.T.y);
	check(Area.S.x,Area.T.y);
	check(Area.T.x,Area.S.y);
	printf("%d %d\n",ans.x,ans.y);
	return 0;
}

7-6 游戏棋盘

题面

查尔明和迈克是好朋友。为了纪念他们之间的友谊,查尔明选了一个有n2块格子的正方形游戏棋盘送给迈克。

由于棋盘太大,不好运送,查尔明只好将它拆成一块块1×1的格子送出。他相信拼好这个棋盘难不倒迈克。

迈克收到礼物后果然不一会儿就根据说明将棋盘拼好了。这个游戏是这样的,每个格子上要么是空的,要么有一枚棋子。在游戏中,迈克需要选定一枚棋子,并将所有与它在同一行或同一列的棋子移动到这一个格子上,其中将位于(x1​,y1​)的棋子移动到(x2​,y2​)的费用为(x1​−x2​)^2+(y1​−y2​)^2。

迈克现在想知道,如果他的操作合法,那么一次操作的总费用的最小值和最大值分别是多少?

输入格式:

第一行包含一个正整数n,表示正方形棋盘的边长。

接下来n2行每行描述一个格子,格式如下:

·首先是一个数x,若x=0则表示这个格子没有棋子,若x=1则表示这个格子有一个棋子。

·接下来是一个数cnt,表示与该棋子有公共边的棋子的个数。

·接下来包含cnt个正整数,依次表示与该棋子有公共边的棋子的编号。

输出格式:

包含一行两个整数,分别表示总费用的最小值和最大值。

输入样例:

3
1 3 3 9 6
0 2 8 7
1 2 1 8
0 3 9 6 5
1 2 4 7
1 4 1 8 4 7
0 3 6 2 5
1 3 3 6 2
1 2 1 4

输出样例:

2 9

样例说明:

222-1.png

对于100%的数据,0≤x≤1,2≤cnt≤4,1≤棋子数≤n2。

222-2.png

时间限制 400 ms 内存限制 64 MB

解题思路

题目描述有点小问题。大致意思就是我们有一个NxN的图,和n*n个碎片需要往里面放,然后给出的每个碎片相邻的碎片编号。拼出图之后再做一些事情。

先考虑图怎么建。考虑只有四个角的相邻数是2,我们随便找一个放在左上角(1,1),这样操作后(1,2),(2,1)的碎片也唯一确定了,在之后全部图就都唯一确定了。

有了图之后我们考虑费用怎么算,(x-x0)^2=x^2-2xx0+x0^2,当y确定的时候,我们预处理出x^2,-2x,以及棋子个数,这样枚举这一行的棋子是,行的贡献可以O(1)算出。列也同理。

最后遍历每个棋子求答案即可。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=505;
typedef long long LL;
int g[maxn*maxn][5],a[maxn][maxn],n;
bool tag[maxn*maxn],vis[maxn*maxn];
LL x1[maxn],x2[maxn],Y1[maxn],y2[maxn],cntx[maxn],cnty[maxn],Max=0,Min=1e18;
inline int read(){
	int ret=0; char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		ret=((ret+(ret<<2))<<1)+ch-'0',ch=getchar();
	return ret;
}
int get(int x,int y){
	if (x==0)
		swap(x,y);
	if (y==0){
		for (int i=1;i<=g[x][0];i++)
			if (!vis[g[x][i]])
				return g[x][i];
	} else{
		for (int i=1;i<=g[x][0];i++)
			if (!vis[g[x][i]]){
				for (int j=1;j<=g[g[x][i]][0];j++)
					if (g[g[x][i]][j]==y)
						return g[x][i];
			}
	}
	return -1;
}
void Build(){
	vis[a[1][1]]=1;
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++){
			if (i==1&&j==1)
				continue;
			a[i][j]=get(a[i][j-1],a[i-1][j]);
			assert(a[i][j]!=-1);
			vis[a[i][j]]=1;
		}
	// for (int i=1;i<=n;i++,putchar('\n'))
	// 	for (int j=1;j<=n;j++)
	// 		printf("%d ",a[i][j]);
}
int main(){
	n=read();
	for (int i=1;i<=n*n;i++){
		int x=read();
		tag[i]=x==1;
		g[i][0]=read();
		for (int j=1;j<=g[i][0];j++)
			g[i][j]=read();
		if (g[i][0]==2)
			a[1][1]=i;
	}
	Build();
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
			if (tag[a[i][j]])
				Y1[i]+=j*j,y2[i]+=-2*j,cnty[i]++;

	for (int j=1;j<=n;j++)
		for (int i=1;i<=n;i++)
			if (tag[a[i][j]])
				x1[j]+=i*i,x2[j]+=-2*i,cntx[j]++;

	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
			if (tag[a[i][j]]){
				LL res=0;
				res+=x1[j]+x2[j]*i+cntx[j]*i*i;
				res+=Y1[i]+y2[i]*j+cnty[i]*j*j;
				Max=max(Max,res),Min=min(Min,res);
			}
	printf("%lld %lld\n",Min,Max);
	return 0;
}

7-7 比特战争

题面

在比特世界,A国正与B国爆发着战争!B国有n个城市,编号依次为1到n。这些城市之间通过m条双向道路连接,其中第i条道路连接着ui​,vi​这两个城市。任意两个城市之间可能有多条道路,也有可能从1号点出发不能到达所有城市。

对于第i个城市,占领这座城市则需要在这里聚集ai​个特种兵,而在这里空降1个特种兵的代价为bi​。对于第i条道路,占领这条道路需要在道路两端点的城市累计聚集ci​个特种兵,即:假如一条边连接着1号和2号城市,而c=9,那么你可以在1号城市聚集3个特种兵,在2 号城市聚集6个特种兵。

A国的目标是占领B国所有的城市(不需要占领所有道路),对于占领过的城市和道路,即使从这里撤兵,它也将永远属于A国。请写一个程序,帮助A国以最小的总代价占领B国所有的城市。

输入格式:

第一行包含两个正整数n,m,表示城市数和道路数。

接下来n行,每行两个正整数ai​,bi​,分别表示每个城市的相关参数。

接下来m行,每行三个正整数ui​,vi​,ci​(ui​=vi​),分别表示每条双向道路的相关参数。

输出格式:

输出一行一个整数,即占领B国所有城市的最小总代价。

输入样例:

3 2
10 5
20 10
10 3
1 2 22
2 3 200

输出样例:

140

对于100%的数据,ai​,bi​,ci​≤10000。

333.png

时间限制 400 ms 内存限制 64 MB

解题思路

先考虑下最后答案是什么样子的:一定是一堆联通块,且单个联通块的费用为:max(a_max,c_max)*b_min。即我们保证这个联通块联通,兵力至少要为最大边权,要战领全部城市,至少需要max(ai),所以最少兵力是两者取max,而派兵我们一定在bi最小的城市。(一次派所有兵,然后全部扫荡!)

这样子可以用状压DP拿到50%的分数。

我们再考虑:哪些边是有效的呢?显然一定是最小生成树上的边。因为若不在最小生成树上,我们联通块内部一定会引入更大的边权,这样子是会让答案更劣的。

我们再考虑状压的必要性:

做kruscal,枚举边的时候,我们考虑这条边<u,v>是否引入:

如果引入,那么所有小于该边边权的边构成的图g中,与u,v相连的点一定会被归入同一个联通块(因为不考虑占领,能通过当前边,就一定能通过其它所有边,而占领城市的开销越合并只会越小,所以一定合并)。

如果不引入,对答案的贡献就是u,v各自联通块贡献的和,直到他们被更大边权的边统一在一起。

记f[i]表示i所在的联通的贡献,则转移方程:

f[u]=min(f[u]+f[v],(max(Maxa[u],Maxa[v],]边权w)*min(Minb[u],Minb[v]))

我们用并查集维护Maxa与Minb和联通性,即可AC

AC代码

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5,maxe=2e5+5;
typedef long long LL;
struct EdgeData{
	int x,y,w;
	inline bool operator <(const EdgeData &B){
		return w<B.w;
	}
} g[maxe];
int n,m,Maxa[maxn],Minb[maxn],fa[maxn];
LL f[maxn],ans;
bool vis[maxn];
inline int read(){
	int ret=0; char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		ret=((ret+(ret<<2))<<1)+ch-'0',ch=getchar();
	return ret;
}
inline int get_fa(int x){
	return fa[x]==x?x:fa[x]=get_fa(fa[x]);
}
inline int max(int a,int b,int c){
	return max(max(a,b),c);
}
int main(){
	n=read(),m=read();
	for (int i=1;i<=n;i++){
		Maxa[i]=read(),Minb[i]=read();
		f[i]=Maxa[i]*Minb[i];
		fa[i]=i;
	}
	for (int i=1;i<=m;i++)
		g[i].x=read(),g[i].y=read(),g[i].w=read();
	sort(g+1,g+m+1);
	int cnt=0;
	for (int i=1;i<=m;i++){
		int fx=get_fa(g[i].x),fy=get_fa(g[i].y);
		if (fx==fy)
			continue;
		fa[fx]=fy,cnt++;
		f[fy]=min(f[fx]+f[fy],(LL)max(Maxa[fx],Maxa[fy],g[i].w)*min(Minb[fx],Minb[fy]));
		Maxa[fy]=max(Maxa[fx],Maxa[fy]);
		Minb[fy]=min(Minb[fx],Minb[fy]);
	}
	memset(vis,0,sizeof(vis));
	ans=0;
	for (int i=1;i<=n;i++){
		int fx=get_fa(i);
		if (!vis[fx])
			ans+=f[fx],vis[fx]=1;
	}
	printf("%lld\n",ans);
	return 0;
}

  • 29
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值