ZOJ3206 Disaster Area Reconstruction ZOJ 3211 Dream City ZOJ 3212 K-Nice

省赛前热身做的题目,有难度的做出来的 放出来一下,印象更深刻,

在此打个小广告,都说咱们信息学院的不会享受生活不文雅,那么在此给一篇文章:让大家陶冶陶冶情操哈哈哈           http://www.sanwen.net/subject/3628849/


先来3206这道题目跟队友一起讨论WA了挺多把做出来的,题意是给你N个村庄,M条道路,道路是单向的,让你再建一条道路,使得这个图的连通分量最大,同时请输出最大连通分量 和 你所建的道路,

一开始完全想错,想到树链剖分,后来想暴力,看直接不行 就先进行强连通缩点,缩点完成后用DFS来进行查找,可惜超时,最后因为DFS发现有某些部分是重合查找,所有有些部分查找以后是可以递归返回值的,那么就是有递推的意思了,于是想到了树状DP,于是开始用树状DP,  WA了有七把吧,给出代码

总体思路就是强连通缩点,然后进行树状DP


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<vector>
#include<stdlib.h>
#define L(x) (x<<1)
#define R(x) (x<<1|1)
#define Mid(x,y) ((x+y)>>1)
using namespace std;
#define N 50010
//N为最大点数
#define M 501001
//M为最大边数
int n, m;//n m 为点数和边数

struct Edge{
	int from, to, nex;
}edge[M];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u]};
	edge[edgenum] = E;
	head[u] = edgenum++;
}

int DFN[N], Low[N], Stack[N], ttop, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始
vector<int>G[N];
void tarjan(int u ,int fa){  
	DFN[u] = Low[u] = ++ Time ;  
	Stack[ttop ++ ] = u ;  
	Instack[u] = 1 ;  

	for (int i = head[u] ; ~i ; i = edge[i].nex ){  
		int v = edge[i].to ;  
		if(DFN[v] == -1)
		{  
			tarjan(v , u) ;  
			Low[u] = min(Low[u] ,Low[v]) ;
		}  
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ; 		
	}  
	if(Low[u] == DFN[u]){  
		int now;
		taj ++ ; bcc[taj].clear(); G[taj].clear();
		do{
			now = Stack[-- ttop] ;  
			Instack[now] = 0 ; 
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}

void tarjan_init(int all){
	for(int i = 0;i<=all;i++)DFN[i]=-1,Instack[i] = 0;
	ttop = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}

void suodian(){
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];
		if(u!=v)G[u].push_back(v);
	}
}
//
int uu, vv, ans;
int siz[N];
int ch[N];
void dfs(int now, int TOP){
	int v, TOP1=1000000;
	int now1 = bcc[now][0], TOPP = bcc[TOP][0];
	for(int i = 0;i < G[now].size();i++){
		v = G[now][i];
		if(siz[v]==0)dfs(v,TOP);
	}
	int maxn = 0;
	for(int i =0;i<G[now].size();i++){
		v = G[now][i];
		if(maxn<siz[v])maxn = siz[v],TOP1=ch[v];
		else if(maxn == siz[v])
			TOP1=min(TOP1,ch[v]);
	}
	maxn += bcc[now].size();
	if(G[now].size())ch[now] = TOP1;
	siz[now] = maxn;
	if(now==TOP)
	{
		now = bcc[now][0];
		if(ans<maxn){ans = maxn;uu=TOP1;vv=now;}
		else if(ans==maxn){
			if(uu>TOP1){uu=TOP1; vv=now;}
			else if(uu==TOP1)vv = min(vv,now);
		}
	}
}
void init(){
	for(int i =1;i<=n;i++)head[i]=-1,siz[i] = 0; edgenum=0;
}

int main() {
	int t;
	int k, i,j;
	scanf("%d",&t);
	while(t--) {
		scanf("%d%d",&n,&m);
		if(m==0){printf("1\n1 2\n");continue;}
		init();
		while(m--){
			int u,v;
			scanf("%d%d",&u,&v);
			add(u,v);
		}
		tarjan_init(n);
		if(taj==1){printf("%d\n1 2\n",n);continue;}
		suodian();
		ans = 0;
		for(i=1;i<=taj;i++)ans = max(ans,(int)bcc[i].size());
		uu = 1, vv= 2;
		for(i=1;i<=taj;i++)sort(bcc[i].begin(),bcc[i].end());
		for(i=1;i<=taj;i++)if(G[i].size()==0)ch[i] = bcc[i][0], siz[i] = bcc[i].size();
		for(i = 1;i<=taj;i++)if(siz[i]==0)
		{
			dfs(i,i);
		}
		printf("%d\n%d %d\n",ans,uu,vv);
	}
	return 0;
}
/*
99
2 1
1 2
2 0
2 2
1 2
2 1


5 4
1 3
2 3
3 4
3 5

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

6 6
1 2
2 3
3 1
4 5
5 6
6 4

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

*/

接下来3211

这是一道DP的题目,

意思是 给你N棵树,M天时间,一天砍一棵树,如果你有一天不砍,那么以后就不能再砍了,树是摇钱树,所以一开始树上长了一些金币,每天还会长金币,输入会给出每棵树初始金币和每天会生长的金币,问你第M天最多可以得到多少金币,我的DP方程是二维的,而且最终题目要的答案就是 dp[n][m],那DP方程的含义就不用多说了 ,状态转移方程就是

dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] + node[i].a + node[i].b * (j - 1));
在进行DP之前要进行排序,以那个 每天生长的金币数从小到大来排,具体原因我不清楚,我就是假设n ==m的时候,那肯定是 按照每天生长的金币数来排序的,所以就YY了一下 没想到 1A


#include<iostream>
#include<cstdio>
#include<list>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<cmath>
#include<memory.h>
#include<set>

#define ll long long

#define eps 1e-8

#define inf 0xfffffff

//const ll INF = 1ll<<61;

using namespace std;


typedef struct Node {
	int a,b;
};

Node node[1000 + 5];
int dp[1000+ 5][1000 + 5];

void clear() {
	memset(node,0,sizeof(node));
	memset(dp,0,sizeof(dp));
}

bool cmp(Node x,Node y) {
	return x.b < y.b;
}

int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		int n,m;
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;i++) {
			scanf("%d",&node[i].a);
		}
		for(int i=1;i<=n;i++)
			scanf("%d",&node[i].b);
		sort(node + 1,node + 1 + n,cmp);
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) {
				dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] + node[i].a + node[i].b * (j - 1));
			}
		}
		printf("%d\n",dp[n][m]);
	}
	return 0;
}
接下来是3212,题意 给你一个N*M的矩阵,如果矩阵中有一个元素等于它上下左右四个相邻元素的和 的话,那就有一个nice,让你求出一个 k nice的矩阵,因为以前浙大月赛遇到过一道构造类的题目,所以这道题很快花了8分钟就A了,

说说我的思想过程,拿第一个案例来说明,我们先把矩阵全部写成 0,因为构造类的案例输出是不唯一的,只要符合题目要求即可


00000

00000

00000.

00000

那么此时 矩阵正中央的6个0是符合题目要求的,所以此时的矩阵是6 nice矩阵,第一个案例要求是3nice,那么我们破坏三个

01230

00000

00000

00000

此时的矩阵就符合了,那么破坏掉的数目就是 (n-2)*(m-2) - k,那么我们会发现 只要每一行除却首尾从1到(n-2)*(m-2) - k填上去即可

不放心再检验第二个案例

01230

04560

00000

00000

看看 是不是(n-2)*(m-2)-k刚好等于6,在不放心再多写几个


#include<iostream>
#include<cstdio>
#include<list>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<cmath>
#include<memory.h>
#include<set>

#define ll long long

#define eps 1e-8

#define inf 0xfffffff

//const ll INF = 1ll<<61;

using namespace std;


int main() {
	int t;
	int n,m,k;
	scanf("%d",&t);
	while(t--) {
		scanf("%d %d %d",&n,&m,&k);
		int tmp = (n - 2) * (m - 2) - k;
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				if(j == 0)
					printf("0 ");
				else if(j == m - 1)
					printf("0\n");
				else {
					if(tmp == 0)
						printf("0 ");
					else
						printf("%d ",tmp--);
				}
			}
		}
	}
	return EXIT_SUCCESS;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值