最后一次图论数据结构练习

最后一次单独拉出来练,虽然觉得还是有太多不足了

Petya and File System

题意:给你几个文件路径,然后问你存放文件和文件夹最多的文件夹是哪个

思路:两个的答案肯定都来自与最上层的某一个文件夹,我们只需要map记录每个文件的路径即可

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4+5;
map<string,int>folder;
//map<string,int>f;
int fa[maxn];
//int fa2[maxn];
int sz2[maxn];
int sz[maxn];
int n;
void inits(){
    for(int i = 0;i < maxn-1;i++){
        fa[i] = i;
        sz[i] = 1;
        //fa2[i] = i;
        sz2[i] = 1;
    }
}
int read(string & file,int loc){
    loc++;
    int flag = 0;
    while(file[loc]!='\\'){
        if(file[loc] == '.'){
            flag = 1;
            break;
        }
        loc++;
    }
    if(flag == 1){
		return file.size();
	}
	return loc;
}
int get(int x){
    if(fa[x]==x)return x;
    return fa[x] = get(fa[x]);
}
void merge(int u,int v){
    int p = get(u);
    int q = get(v);
    if(p!=q){
        fa[q] = p;
        sz[p] += sz[q];
    }
}
//int get2(int x){
//    if(fa2[x]==x)return x;
//    return fa2[x] = get(fa2[x]);
//}
//void merge2(int u,int v){
//    int p = get2(u);
//    int q = get2(v);
//    if(p!=q){
//        fa2[q] = p;
//        sz2[p] += sz2[q];
//    }
//}
int main(){
    string s;
    int cnt = 0;
    int ct = 0;
    inits();
    while(cin>>s){
        
        int p = 2;
        int n = s.size();
        
        int st = -1;
        while(p<n){
            int las = read(s,p);
            string tmp = s.substr(0,las);
            //  cout<<tmp<<endl;
            if(las<n){
                int k = -1;
                if(folder.count(tmp)==0){
                    folder[tmp] =  ++cnt;
                    // cout<<"KK"<<endl;
                }
                k = folder[tmp];
                if(st == -1){
                    st = k;
			    }
                // cout<<tmp<<"##"<<cnt<<endl;
                merge(st,k);
            }
            else {
                //int k = -1;
                //if(f.count(tmp)==0){
                 //f[tmp] =  ++ct;
                //}
                //k = f[tmp];
                //merge2(st,k);
            	sz2[st]++;
			}
            p = las;
        }
        
    }
    int ans = 0;
    for(int i = 1;i <= cnt;i++){
        ans = max(ans,sz[get(i)]);
    }
    cout<<ans-1<<" ";
    ans = 0;
    for(int i = 1;i <= cnt;i++){
        //ans = max(ans,sz2[get2(i)]);
    	ans = max(ans,sz2[i]);
	}
    cout<<ans-1<<endl;
}

Trucking

题意:给你一个无向图,然后每个边上有限高和限长度,然后有一个货车从起点出发到达终点,问,在保证限高最大的情况下,最短路是多少

思路:
使用了spfa迭代维护了限高的最大值,然后在按照我们求得的限高跑一边dijstra就好了

网上的思路是二分枚举限高,然后spfa或者dijstra跑最短路

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e6+5;
const int N = 1005;
struct edge{
	int v,limt,w,next;
}e[maxn<<1];
int head[N],cnt;
void add(int u,int v,int limt ,int w){
	e[cnt].v = v;
	e[cnt].limt = limt;
	e[cnt].w = w;
	e[cnt].next = head[u];
	head[u] = cnt++;
}
queue<int>q;
int mxh[N];
int dis[N],vis[N],ct[N];
int n,m;
void inits(){
	for(int i = 0 ;i <= n;i++){
		head[i] = -1;
		dis[i] = INF;
		mxh[i] = vis[i] = ct[i] = 0;
	}
	cnt = 0;
	while(!q.empty())q.pop();
}
int SPFA(int s,int ed,int h){
	q.push(s);
	vis[s] = 1;ct[s] = 1;dis[s] = 0;
	mxh[s] = h;
	while(!q.empty()){
		int now = q.front();
		q.pop();
		vis[now] = 0;
		//cout<<now<<"##"<<mxh[now]<<endl;
		for(int i = head[now];~i;i=e[i].next){
			int v = e[i].v;
			int w = e[i].w;
			int limt = e[i].limt;
			if(mxh[v] < min(mxh[now],limt)){
				//dis[v] = dis[now] + w;
				mxh[v]  = min(mxh[now],limt);
				if(!vis[v]){
					ct[v]++;
					vis[v] = 1;
					q.push(v);
					if(ct[v]>n)return 1;
				}
			}
		}
	}
	return 0;
}
struct Node{
	int id , d;
	bool operator < (const Node &x)const{
		return d > x.d;
	}
};
priority_queue<Node>Q;
void dijkstra(int s,int ed,int h){
	while(!Q.empty())Q.pop();
	memset(vis,0,sizeof vis);
	Q.push(Node{s,0});
	dis[s] = 0;
	while(!Q.empty()){
		int u = Q.top().id;Q.pop();
		if(vis[u])continue;
		vis[u] = 1;
		for(int i = head[u];~i;i=e[i].next){
			int v = e[i].v;
			int l = e[i].limt;
			if(l < h)continue;
			if(dis[v]>dis[u] + e[i].w){
				dis[v] = dis[u] + e[i].w;
				Q.push(Node{v,dis[v]});
			}
		}

	}
}
int main(){
	int tt = 0;
	while(1){
		scanf("%d%d",&n,&m);
		if(n==0&&m==0)break;
		inits();
		for(int i = 0;i < m;i++){
			int u,v,limt,w;
			scanf("%d%d%d%d",&u,&v,&limt,&w);
			if(limt==-1)limt = INF;
			add(u,v,limt,w);add(v,u,limt,w);
		}
		int st,ed,h;
		scanf("%d%d%d",&st,&ed,&h);
		SPFA(st,ed,h);
		dijkstra(st,ed,mxh[ed]);
		if(tt!=0)printf("\n");
		printf("Case %d:\n",++tt);
		if(dis[ed]>=INF)printf("cannot reach destination\n");
		else {
			printf("maximum height = %d\n",mxh[ed]);
			printf("length of shortest route = %d\n",dis[ed]);
		}
	}
}

maximum shortest distance

题意:
在一张平面上给定一些点,然后让你选取k个点,使得选取点集中两点距离的最小值最大

思路:
题解说看到这种最小值最大的情况,就是二分,我确实也想到了二分实数,但是剩下的部分我想错了,应该是剩下求一个最大团,就是保证我们答案的同时建一个新图,然后求一个最大团,抄了网上板子过了

#include <bits/stdc++.h>
using namespace std;
const int maxn = 55;
const double esp = 1e-4;
double mp[maxn][maxn];
struct Node{
	int x,y;
}node[maxn];
int k;
 
int mx;//最大团数(要初始化为0)
int vis[maxn], tuan[maxn];
int can[maxn][maxn];//can[i]表示在已经确定了经选定的i个点必须在最大团内的前提下还有可能被加进最大团的结点集合
int num[maxn];//num[i]表示由结点i到结点n构成的最大团的结点数
bool g[maxn][maxn];//邻接矩阵(从1开始)
int n, m;
 
bool dfs(int tot, int cnt) {
    if(tot == 0) {
        if(cnt > mx) {
            mx = cnt;
            for(int i = 0; i < mx; i++) {
                tuan[i] = vis[i];
            }
            return true;
        }
        return false;
    }
    for(int i = 0; i < tot; i++) {
        if(cnt + (tot - i) <= mx) return false;
        if(cnt + num[can[cnt][i]] <= mx) return false;
        int k = 0;
        vis[cnt] = can[cnt][i];
        for(int j = i + 1; j < tot; j++) {
            if(g[can[cnt][i]][can[cnt][j]]) {
                can[cnt + 1][k++] = can[cnt][j];
            }
        }
        if(dfs(k, cnt + 1)) return false;
    }
    return false;
}
 
void maxclique() {
    mx = 1;
    for(int i = n; i >= 1; i--) {
        int k = 0;
        vis[0] = i;
        for(int j = i + 1; j <= n; j++) {
            if(g[i][j]) {
                can[1][k++] = j;
            }
        }
        dfs(k, 1);
        num[i] = mx;
    }
}


double MKDis(Node a,Node b){
	return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}
//int n,k;
bool ok(double x){
	memset(g,0,sizeof g);
	for(int i = 1;i<=n;i++){
		for(int j = 1; j< i;j++){
			if(MKDis(node[i],node[j]) >= x)g[i][j] = g[j][i] = 1;
		}
	}
	maxclique();
	if(mx >= k)return true;
	return false ;
}
int main(){
	while(cin>>n>>k){
		for(int i = 1;i <= n; i++){
			scanf("%d%d",&node[i].x,&node[i].y);
			for(int j = 1;j < i; j++){
				mp[i][j] = mp[j][i] = MKDis(node[i],node[j]);
			}
		}
		double l = 0,r = 1e9;
		double ans = 0;
		while(r-l > esp){
			double mid = (l+r)/2;
			if(ok(mid)){
				// ans = max(ans,mid);
				l = mid;
			}
			else r = mid;
		}
		// cout<<ans<<endl;
		printf("%.2f\n",l);
	}
	
}

Bicolorings

题意:给你一个2*n的方格,然后染黑白两色,问最后染出来的色块有k个的情况到底有多少种染色方法

思路:线性dp, d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前染色到前i个方块,染出色块j个的答案数
思路非常好想,就是下一个位置可以由上一个位置装换过来,并且可以优化掉一维,然后还有一种状压dp的思想记录最后一列的情况,但是调试了很久。
最终确定第一维转移是位置转移,由i-1转移到i,第二位转移为情况属染出色块个数的转移,由于最后一行和倒数第二行分布,我们可以写出dp转移公式,然后倒序转移!因为我们转移是按照当前行的转移到下一行,我们优化了一维就要倒序转移,另外为了放置内部影响,我们要那变量倒换一下。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1005;
const int mod = 998244353;
ll dp[maxn<<1][10];
int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	dp[1][0] = 1;
	dp[1][1] = dp[1][2] = 0;
	dp[1][3] = 1;
	dp[2][1] = dp[2][2] = 1;
	// dp[0][0] = dp[0][1] = dp[0][2] = dp[0][3] = 1;
	
	
	for(int j = 2;j <= n;j++){
		for(int i = k;i >= 2;i--){
			ll a = (dp[i][0] + dp[i][1] + dp[i][2] + dp[i-1][3])%mod;
			ll b = (dp[i-1][0] + dp[i][1] + dp[i-2][2] + dp[i-1][3])%mod;
			ll c = (dp[i-1][0] + dp[i-2][1] + dp[i][2] + dp[i-1][3])%mod;
			ll d = (dp[i-1][0] + dp[i][1] + dp[i][2] + dp[i][3])%mod;
			dp[i][0] = a;dp[i][1] = b;dp[i][2] = c;dp[i][3] = d;
		}
	}
	cout<<(dp[k][0] + dp[k][1] + dp[k][2] + dp[k][3])%mod;
}

Armchairs

题意:给你 n n n个空位置,然后给你n/2以下个被占了的座位,然后需要我们将被占了的座位上的人都移动到其他的空位置上,代价是 ∣ i − j ∣ |i-j| ij问你最小花费

思路:和我上一个写的题解的一道题思路相仿,就是n个物品,放置在n个位置,然后放置在每个位置都有对应的花费,求我们的最小花费,就是一个背包,体积为n每个物品的价值为 ∣ i − j ∣ |i-j| ij
然后就是注意两个维度都要从小往大排一个序,贪心想法,保证无后效性

网上的思路:比较裸的最小费用最大流
我们将源点和每一个被占了的座位连接,流量为1,费用为0,每一个没被占的座位和汇点连接,流量为1,费用为0,每两个座位之间连一条边,流量为n nn,费用为1,跑E K即可

AC代码

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 5005;
int a[maxn],b[maxn];
int dp[maxn];
int main(){
	int n;
	scanf("%d",&n);
	int cta = 0,ctb = 0;
	for(int i = 1;i <= n;i++){
		int x;scanf("%d",&x);
		if(x==0){
			a[++cta] = i;
		}
		else b[++ctb] = i;
	}
	memset(dp,INF,sizeof dp);
	dp[0] = 0;
	for(int i = 1;i <= cta;i++){
		for(int j = ctb;j >= 1;j--){
			dp[j] = min(dp[j],dp[j-1] + abs(a[i]-b[j]));
		}
	}
	cout<<dp[ctb]<<endl;
}

Equivalent Sets

  • HDU - 3836
    题意:给你一堆集合的关系,然后问再给你多少关系可以把他们变成等价集合。
    思路:
    首先,我们有结论就是如果一个强连通分量就是等价的,所以我们先缩点,然后看剩下的点的入度为0的个数和出度为0个数的最大值。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4+5;
const int maxn = 5e4 + 5;
int n,m;
struct edge{
	int v,next;
}e[maxn<<1];
int head[N],cnt;
void add(int u,int v){
	e[cnt].v = v;
	e[cnt].next = head[u];
	head[u] = cnt++;
}
int dfn[N],low[N],c[N];
int ins[N];
int st[N],top = 0;
int num = 0;
int lis_nu = 0;
int ind[N],outd[N];
void inits(){
	cnt = 0;
	memset(head,-1,sizeof head);
	for(int i = 0;i <= n;i++){
		dfn[i] = low[i] = ins[i] = c[i] = 0;
		ind[i] = outd[i] = 0;
	}
	top = 0;
	lis_nu = 0;
	num = 0;
}
void Tarjan(int x){
	low[x] = dfn[x] = ++num;
	st[++top] = x;
	ins[x] = 1;
	for(int i = head[x];~i;i = e[i].next ){
		int v = e[i].v;
		if(!dfn[v]){
			Tarjan(v);
			low[x] = min(low[x],low[v]);
		}
		else if(ins[v])low[x] = min(low[x],dfn[v]);
	}
	if(low[x]==dfn[x]){
		int t;
		lis_nu++;
		do{
			t = st[top--];
			c[t] = lis_nu;
			ins[t] = 0;
		}while(t!=x);
	}
}
struct Qu{
	int u,v;
}qu[maxn];
int main(){
	while(~scanf("%d%d",&n,&m)){
		inits();
		for(int i = 0;i < m; i++){
			int u,v;
			scanf("%d%d",&u,&v);
			add(u,v);
			qu[i].u = u;qu[i].v = v;
		}
		for(int i = 1;i<=n;i++){
			if(!dfn[i])Tarjan(i);
		}
		for(int i = 0;i < m;i++){
			int u = c[qu[i].u];
			int v = c[qu[i].v];
			if(u==v)continue;
			ind[v]++;
			outd[u]++;
		}
		int mxin = 0,mxout = 0;
		for(int i = 1;i <= lis_nu;i++){
			if(ind[i]==0){
				mxin++;
			}
			if(outd[i]==0){
				mxout++;
			}
		}
		printf("%d\n",lis_nu==1?0:max(mxin,mxout));
	}
}

Zjnu Stadium

题意:
一个300的体育场,然后是一圈样子的,然后我们可以指定两个人的位置关系,不如a在b右边30个队列出,然后问你有几个关系是错的
思路:
边带权的板子题:
然后就是注意正确的维护集合之间的关系。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5+5;
const int mod = 300;
int fa[maxn];
int sz[maxn];
int d[maxn];
int get(int x){
	if(fa[x]==x)return x;
	int root = get(fa[x]);
	d[x] = (d[x] + d[fa[x]])%mod;
	return fa[x] = root;
}
void merge(int u,int v,int w){
	int p = get(u);
	int q = get(v);
	if(p!=q){
		fa[p] = q;
		d[p] = (w - d[u] + d[v] + mod)%mod;
	}
}
int main(){
	int n,m;
	while(~scanf("%d%d",&n,&m)){
		for(int i = 0;i <= n;i++){
			fa[i] = i;
			d[i] = 0;
		}
		int ans = 0;
		for(int i = 0;i<m;i++){
			int u,v,w;
			scanf("%d%d%d",&u,&v,&w);
			if(get(u)==get(v)){
				if((d[u]-d[v]+mod)%mod!=w)ans++;
			}	
			else merge(u,v,w);
		}
		printf("%d\n",ans);
	}
}

Mike and gcd problem

题意:
给你一个序列a,然后可以有如下操作,
delete numbers a i ,   a i   +   1 a_i, a_i + 1 ai,ai+1 and put numbers a i   −   a i   +   1 ,   a i   +   a i   +   1 a_i - a_i + 1, a_i + a_i + 1 aiai+1,ai+ai+1 in their place instead, 然后问最少多少次这样的操作可以使整个序列的gcd大于1,然后gcd(0,2)=2.
思路:
先直接正向维护一遍gcd,如果已经是大于1的我们就不需要任何操作了,如果不是我们就需要改变一些数,构造他们的gcd为2,想一想为什么,因为gcd=1肯定有互质的数,我们如何将这个互质的数变成有公共因子的数,就是把所有数的公共因子变成2,我们就正向扫描把奇数和奇数变成两个偶数,然后把奇数和偶数变成两个偶数,注意这样需要两次操作。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int gcd(int a,int b){
	if(b==0)return a;
	return gcd(b,a%b);
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i = 1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	int g = a[1];
	for(int i = 2;i<=n;i++){
		int k = a[i];
		if(g<a[i])swap(g,k);
		g = gcd(g,k);
	}
	if(g>1){
		printf("YES\n0");return 0;
	}
	int ans = 0;
	for(int i = 1;i <= n;i++){
		if(a[i]%2==0)continue;
		if((a[i]&1)&&(a[i+1]&1)){
			ans += 1;
			a[i] = 2;a[i+1] = 2;
		}
		else ans += 2;
	}
	printf("YES\n");
	printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值