西南联训5[Source from NK] 题解&总结

Problem 1

数正方形(count.cpp/c/pas)

题目描述

n * n的点阵中任取4个点,回答:

问题1:这4个点恰好是“正放”的正方形的4个顶点的方案数是多少?

问题2:这4个点恰好是正方形(包括“正放”和“斜放”)4个顶点的方案数是多少?


下图为一个4*4的点阵,左图表示一种“正放”的方案,右图表示一种“斜放”的方案。

    

输入格式

两个整数nkn表示点阵的尺寸,k=1表示需要回答问题1k=2表示需要回答问题2

输出格式

一个整数,表示答案。(模1000000007再输出)

输入样例

输入样例1:

4 1

输入样例2:

4 2

输出样例

输出样例1:

14

输出样例2

20

数据范围

对于10%的数据,n = 5   其中k=1k=2各占一半

对于30%的数据,1 <= n <= 50    其中k=1k=2各占一半

对于100%的数据,1 <= n <= 100000      其中k=1k=2各占一半

样例说明

 这题其实没什么好说的,就是一个找规律而已,唯一要注意的地方就是取模的顺序,这个地方只要多试几组大样例就能发现问题

期望得分:100,实际得分:100

<span style="color:#000000;">#include<cstdio>
#include<iostream>
#define LL long long
using namespace std;
const LL mod=1000000007;
LL n,k;
int main(){
	cin>>n>>k;
	LL i,j,sum;
	sum=(((n-1)*n)*(2*n-1)/6)%mod;
	if(k==1){
	    cout<<sum;	
	    return 0;
	}
	for(i=n-2;i;i--)
	    sum=(sum+i*i%mod*(n-i-1)%mod)%mod;
	cout<<sum;
}
/*
98765 2
*/</span>



Problem 2

取数(choose.cpp/c/pas)

题目描述

    n个整数组成的一个环,现在要从中取出m个数,取走一个数字就不能取跟它相邻的数字(相邻的数不能同时取)。要求取出的数字的总和尽可能大,问这个最大和是多少? 如果无解,请输出“Error!

输入格式

第一行包含两个正整数nm

第二行为n个整数Ai

输出格式

仅一个整数,表示所求结果。如果无解输出“Error!”,不包含引号。

输入样例

输入样例1

输入样例2

样例输入3

7 3

1 2 3 4 5 6 7

7 4

1 2 3 4 5 6 7

8 4

8 5 6 2 3 4 8 9

输出样例

输出样例1

输出样例2

样例输出3

15

Error!

25

数据范围

对于全部数据:m<=n;-1000<=Ai<=1000

数据编号

N的大小

数据编号

N的大小

1

40

11

2013

2

45

12

5000

3

50

13

10000

4

55

14

49999

5

200

15

111111

6

200

16

148888

7

1000

17

188888

8

2010

18

199999

9

2011

19

199999

10

2012

20

200000

 

样例说明

 这题就有一些难度了,当初考试的时候只想到了暴力dp,原本能过55分差不多,再优化一下能过60分,但是数组开成了1000*1000,直接导致挂了8,9,10,11组原本能过的数据

正解:我们先把每个数字以及它的编号拿入堆中。建大根堆。并记录下每个数左右元素的编号。L[k],R[k]。
   最后要输出的答案是ans。初始化为0。  
   然后每次从堆里面拿出堆顶元素,ans+这个堆定元素的值。
   假设我们当前取出的堆顶元素的编号为k。我们现在新生成一个数P[i]=P[L[k]]+P[R[k]]-P[k]。
   编号为i的数左边的数的编号我们赋为L[L[k]]。右边的数赋为R[R[k]]。    然后R[R[k]]的左边是i。L[L[k]]右边是i。( 类似于链表的操作)

我们为什么要这么做呢?
  因为我们知道,每次选出最大的数肯定不能保证拿到的是最优解。所以我们给自己留个退路,新生成的数,就是取消前面的选择,转过来选择他两旁的数(也就是所谓的改悔)。

期望得分:55,实际得分:20

<span style="color:#000000;">#include<cstdio>
#include<iostream>
#include<queue>
#define PAIR pair<int,int>
#define xx first
#define yy second 
using namespace std;
const int maxn=4e5+5;
int n,m,a[maxn],L[maxn],R[maxn],tot,ans;
bool vis[maxn];
priority_queue<PAIR>q;
int main(){
	scanf("%d%d",&n,&m);
	int i,j;
	for(i=1;i<=n;i++){
		scanf("%d",&a[i]);
		L[i]=i-1;
		R[i]=i+1;
		q.push(make_pair(a[i],i));
		tot++;
	}
	L[1]=n,R[n]=1;
	if(m>n/2){
		puts("Error!");
		return 0;
	}
	while(m!=0){
		int id=q.top().yy;q.pop();
		if(vis[id])continue;
		ans+=a[id];
		a[++tot]=a[L[id]]+a[R[id]]-a[id];
		vis[id]=vis[L[id]]=vis[R[id]]=1;
		L[tot]=L[L[id]];
		R[tot]=R[R[id]];
		L[R[R[id]]]=tot;
		R[L[L[id]]]=tot;
		q.push(make_pair(a[tot],tot));
		m--;
	}
	cout<<ans;
}</span>

Problem 3

葡萄酒交易(wine.cpp/c/pas)

题目描述

某地分布着N个村庄,编号0到N-1,每个村庄要么需要买酒,要么需要卖酒。

设第i个村庄对葡萄酒的需求为Ai,其中Ai>0表示该村需要买酒,Ai<0表示该村需要卖酒。所有村庄供需平衡,即所有Ai之和等于0 (∑Ai =0)。

不过,只有M对村庄之间存在贸易往来,其中第i对村庄之间无论运输多少葡萄酒,都要花费Ti的运费。请你计算最少需要多少运费就可以满足所有村庄对酒的需求。

输入格式

第一行两个整数N、M。

第二行N个整数Ai。

接下来M行每行三个整数pi,qi,Ti,表示在编号为pi和qi的村庄之间运酒需要花费Ti的费用。数据保证每对pi、qi最多出现一次。

输出格式

输出一个整数表示答案。无解输出Impossible

输入样例

3 3

50 -20 -30

0 1 10

1 2 20

0 2 100

输出样例

30

数据范围

对于 50% 的数据:2<=N<=8。

对于 100% 的数据:
2<=N<=16
0<=M<=N*(N-1)/2
0<=pi,qi<N
-1000<=Ai<=1000
0<=Ti<=1000

样例说明

 

这题数据比较神奇,直接最小生成树+判断是否有解就能A,不判是否有解就错1组,开始明明想写个最小生成树骗分……,后来发现漏洞百出就放弃了,直接cout"impossible"得了10分,早知道直接搞最小生成树

正解:

1.找出所有酒量和为0的集合。
     若该集合的点是联通的,那么求出该集合的最小生成树,生成树的值即是该集合酒量转移所需最小代价
2.将每一个酒量总和为0的集合看成是一个物品,利用背包动规求出最优解。

用二进制来压缩状态,1代表节点在集合中,0代表不在。
   比如数字s的二进制形式为100111,表明0,1,2,5号节点在s表示的集合中。
   
   题目最多有n(n<=16)个节点,因此s的范围是0到(2^n)-1 也就是(1<<n)-1
   用数组Sum[s],记录集合s中包含的节点的酒量之和。
   
   对于每一个酒量和为0的集合x(Sum[x]==0),若能得到一棵最小生成树,用数组Cost[x]记录下该生成树的代价
   
   f[i]记录平衡集合i中的节点的酒量值,所需最小代价
   对于集合i和j,若满足Sum[i]==0且Sum[j]==0
   那么有f[i|j]=min(f[i|j],f[i]+Cost[j]);
   i|j表示集合i与集合j合并之后的集合
期望得分:90,实际得分:10

<span style="color:#000000;">#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=18,inf=0x3f3f3f3f;
int n,m,a[maxn],f[1<<maxn],sum[1<<maxn],all,p[1<<maxn],fa[maxn];
int getfa(int x){return x==fa[x]?x:fa[x]=getfa(fa[x]);}
struct node{
	int a,b,c;
	bool operator<(const node& h)const{
		return c<h.c;
	}
}s[maxn*maxn]; 
int kruscal(int s0){
	int i,fx,fy,x,y,cnt=0,tot=0,ans=0;
	for(i=0;i<n;i++){
		fa[i]=i;
		if((s0>>i)&1)tot++;
	}
	for(i=1;i<=m;i++){
		x=s[i].a,y=s[i].b;
		if(((s0>>x)&1)&&((s0>>y)&1)){
			fx=getfa(x),fy=getfa(y);
			if(fx!=fy){
				fa[fx]=fy;
				cnt++,ans+=s[i].c;
			}
		}
	}
	if(cnt+1!=tot)return inf;
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	int i,j;
	for(i=0;i<n;i++)scanf("%d",&a[i]);
	for(i=1;i<=m;i++)scanf("%d%d%d",&s[i].a,&s[i].b,&s[i].c);
	sort(s+1,s+1+m);
	all=(1<<n)-1;
	for(i=0;i<=all;i++)
	    for(j=0;j<n;j++)
	        if((i>>j)&1)sum[i]+=a[j];
	for(i=0;i<=all;i++)
	    if(sum[i]==0)p[i]=kruscal(i);
	    else p[i]=inf;
	memset(f,inf,sizeof(f));
	f[0]=0;
	for(i=0;i<=all;i++){
		if(sum[i]!=0)continue;
		for(j=1;j<=all;j++){
			if(sum[j]!=0)continue;
		    f[i|j]=min(f[i|j],f[i]+p[j]);
		}
	}
	if(f[all]<inf)printf("%d",f[all]);
	else puts("Impossible");
}</span>
总得分:130,排名:18

总结:代码细节处理还是不够,粗心的毛病还没改掉,下次考试前10分钟一定要认真检查一下自己的代码,减少过失性丢分的概率



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值