四、学算法啦啦啦——dp,并查集等


from https://vjudge.net/contest/308052

A - 数字三角形

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

(Figure 1)
Figure 1 shows a number triangle. Write a program that calculates the highest sum of numbers passed on a route that starts at the top and ends somewhere on the base. Each step can go either diagonally down to the left or diagonally down to the right.
Input
Your program is to read from standard input. The first line contains one integer N: the number of rows in the triangle. The following N lines describe the data of the triangle. The number of rows in the triangle is > 1 but <= 100. The numbers in the triangle, all integers, are between 0 and 99.
Output
Your program is to write to standard output. The highest sum is written as an integer.
Sample Input
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30

思路:dp[i][j]=max{ dp[i-1][j] , dp[i-1][j-1] } + v[i][j]
每行每列两端都扩充上0保证递推可用

#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
int main()
{
//  freopen("C:\\Users\\Admin\\Desktop\\input.txt","r",stdin);
    int dp[110][110]={0},v[110][110]={0};
    int n;
    while(cin>>n){
        int i,j,ans=0;
        for(i=1;i<=n;i++){
            j=1;
            for(;j<=i;j++)
                cin>>v[i][j];
        }//stored
        for(i=1;i<=n;i++){
            for(j=1;j<=i;j++){
                dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+v[i][j];
                if(i==n&&dp[i][j]>ans)
                    ans=dp[i][j];
            }
        }
        cout<<ans<<endl;
    }
}

B - LITTLE SHOP OF FLOWERS

赋值0的时候多赋值到了dp[1][0]导致错误

#include <iostream>
#include <string.h>
#define INF 0x3f3f3f3f
using namespace std;
int main()
{
    int f,V,i,j;
    int v[101][101],dp[101][101];
    cin>>f>>V;
    for(i=1;i<=f;i++)
        for(j=1;j<=V;j++)
            cin>>v[i][j];
    memset(dp,-INF,sizeof(dp));
    for(int i=0;i<=100;i++)dp[0][i]=0;//i<=101
    //dp[i][j]表示前i个花插到前j个瓶子的最大价值
    //dp[i][j]=max(dp[i-1][j-1]+v[i][j],dp[i][j-1])  前者是这个花瓶插,后者是不插
    for(i=1;i<=f;i++)
        for(j=1;j<=V;j++)
            dp[i][j]=max(dp[i-1][j-1]+v[i][j],dp[i][j-1]);
    cout<<dp[f][V]<<endl;
    return 0;
}

C - 钉子和小球

从最高往下,遇到一个钉子下面的两个就概率分半,因为是分数,所以不是从1开始分1/2,而是从2^n开始分半。
一直WA是因为只用输入一组数据,然鹅写成了while(cin)
还有50层第一个是2^50int就会爆

#include <cstdio>
#include <string.h>
#include <iostream>
#define ll __int64
using namespace std;
ll pow(int t)
{
    ll a=1;
    for(int i=0;i<t;i++)
        a*=2;
    return a;
}
ll gcd(ll a,ll b)
{
    if(a<b){
        a^=b;
        b^=a;
        a^=b;
    }
    ll r=a%b;
    while(r){
        a=b;
        b=r;
        r=a%b;
    }
    return b;
}
int main()
{
    ll dp[55][55];
    char pic[55][55];
 // freopen("C:\\Users\\Administrator\\Desktop\\input.txt","r",stdin);
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            cin>>pic[i][j];
    memset(dp,0,sizeof(dp));
    dp[1][1]=pow(n);
    for(int i=1;i<=n+1;i++){
        for(int j=1;j<=i;j++){
            if(pic[i][j]=='*'){
                dp[i+1][j]+=dp[i][j]/2;
                dp[i+1][j+1]+=dp[i][j]/2;
            }
            else{
                dp[i+2][j+1]+=dp[i][j];
            }
        }
    }
    ll t=dp[n+1][m+1],g=gcd(t,dp[1][1]);
    if(t==0){
        printf("0/1\n");
    }
    printf("%lld/%lld\n",t/g,dp[1][1]/g);
    return 0;
}

D - 完全背包

多层背包,一年一背包就好
完全和01的区别是完全对于一物品可以选无数次

John never knew he had a grand-uncle, until he received the notary’s letter. He learned that his late grand-uncle had gathered a lot of money, somewhere in South-America, and that John was the only inheritor.
John did not need that much money for the moment. But he realized that it would be a good idea to store this capital in a safe place, and have it grow until he decided to retire. The bank convinced him that a certain kind of bond was interesting for him.
This kind of bond has a fixed value, and gives a fixed amount of yearly interest, payed to the owner at the end of each year. The bond has no fixed term. Bonds are available in different sizes. The larger ones usually give a better interest. Soon John realized that the optimal set of bonds to buy was not trivial to figure out. Moreover, after a few years his capital would have grown, and the schedule had to be re-evaluated.
Assume the following bonds are available:

ValueAnnual interest
4000400
3000250

With a capital of e10 000 one could buy two bonds of $4 000, giving a yearly interest of $800. Buying two bonds of $3 000, and one of $4 000 is a better idea, as it gives a yearly interest of $900. After two years the capital has grown to $11 800, and it makes sense to sell a $3 000 one and buy a $4 000 one, so the annual interest grows to $1 050. This is where this story grows unlikely: the bank does not charge for buying and selling bonds. Next year the total sum is $12 850, which allows for three times $4 000, giving a yearly interest of $1 200.
Here is your problem: given an amount to begin with, a number of years, and a set of bonds with their values and interests, find out how big the amount may grow in the given period, using the best schedule for buying and selling bonds.

Input
The first line contains a single positive integer N which is the number of test cases. The test cases follow.
The first line of a test case contains two positive integers: the amount to start with (at most $1 000 000), and the number of years the capital may grow (at most 40).
The following line contains a single number: the number d (1 <= d <= 10) of available bonds.
The next d lines each contain the description of a bond. The description of a bond consists of two positive integers: the value of the bond, and the yearly interest for that bond. The value of a bond is always a multiple of $1 000. The interest of a bond is never more than 10% of its value.

Output
For each test case, output – on a separate line – the capital at the end of the period, after an optimal schedule of buying and selling.

Sample Input
1
10000 4
2
4000 400
3000 250

Sample Output
14050

for (int i = 1;i <= N;i++)
{
	for (int v = weight[i];v <= V;v++)
	{
		f[v] = max(f[v],f[v - weight[i]] + Value[i]);
	}
}

原文:https://blog.csdn.net/insistGoGo/article/details/11081025
分析:这和01背包的伪代码很相似,在01背包的代码中,v变化的区间是逆序循环的,即[V,Weight[i]]。而这里,v变化的区间是顺序循环的,即为[Weight[i],V]。
原因:

再次给出定义:

f[i][v]表示把前i件物品放入容量为v的背包时的最大代价。

f[i-1][v-c[i]]表示把前i - 1件物品放入容量为v的背包时的最大代价.

在01背包中,v变化的区间是逆序循环的原因:要保证由状态f[i-1][v-c[i]]递推状态f[i][v]时,f[i-1][v-c[i]]没有放入第i件物品。之后,在第i循环时,放入一件第i件物品。

01背包的方程:

f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + Value[i])
在完全背包中,v变化的区间是顺序循环的原因:完全背包的特点是每种物品可选无限件,在求解加选第i种物品带来的收益f[i][v]时,在状态f[i][v-c[i]]中已经尽可能多的放入物品i了,此时在f[i][v-c[i]]的基础上,我们可以再次放入一件物品i,此时也是在不超过背包容量的基础下,尽可能多的放入物品i。

完全背包的方程:

f[i][v] = max(f[i - 1][v],f[i][v - weight[i]] + Value[i]);
f[v]=max {f[v]*旧的* , f[v-weight[i]]*因为考虑的是i物品尽可能加进去了多个,所以必须是新的,比如已经加进去一个,这次考虑的是是否在加过的基础上(新的)再加* + value[i] }

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cstdio>
using namespace std;
int main()
{
//  freopen("C:\\Users\\Admin\\Desktop\\input.txt","r",stdin);
    int w[11],v[11];
    int T,ans,year,d,i,dp[100000];
    cin>>T;
    while(T--){
        cin>>ans>>year>>d;
        for(i=1;i<=d;i++)
            cin>>w[i]>>v[i];
        for(i=1;i<=d;i++)
            w[i]/=1000;
        while(year--){//一次完全背包
            int t=ans/1000;
            memset(dp,0,sizeof(dp));
            for(i=1;i<=d;i++)
                for(int j=w[i];j<=t;j++)
                    dp[j]=max(dp[j], dp[j-w[i]]+v[i]);
            ans+=dp[t];
        }
    cout<<ans<<endl;
    }
}

E - 01背包

分组背包和01背包差不多,只不过多加一个循环判断组内选哪个

Bessie has gone to the mall’s jewelry store and spies a charm bracelet. Of course, she’d like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a ‘desirability’ factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).

Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.

Input
*Line 1: Two space-separated integers: N and M
*Lines 2…N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di

Output
*Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints

Sample Input
4 6
1 4
2 6
3 12
2 7
Sample Output
23

思路:用最低级的二维表格,自底向上求解
大神的详细描述
vi表示价值,wi表示重量,V(i,j)表示j容量下前i个物品可装的最高价值
对于第i个物品,可以选:最后的价值就是V(i-1 , j-wi)+vi
也可以不选:最后的价值就是V(i-1 , j)
但是对于wi>j的不可以选,因此主要逻辑是:

  1. wi>j : V(i,j)=V(i-1,j)
  2. wi <= j : V(i,j)=max { V(i-1,j) , V(i-1,j-wi)+vi}
  3. i=0时,表示没有东西可以装,V(0,j)=0
  4. j=0时,表示没空间,所以V(i,0)=0

因为每次都加了vi所以不用基例
对于V(x,y)需要一步步递推下去,所以我们制表从基层开始反向填
但是这样二维数组不够用啊,直接爆内存
使用static int范围不会炸
但是数据大了会WA,其实是因为没有考虑物品和背包无重量的情况
因此如果M是0,j就不可以从1取起,而应该是0
这次不会WA,但是RE了
因此要用滚动数组,但用的是2M空间
用一维数组是M空间

/*--------------------------WA||RE------------------------*/
#include <iostream>
#include <cmath>
#include <cstdio>
#include <string.h>
using namespace std;
int main()
{
//  freopen("C:\\Users\\Admin\\Desktop\\input.txt","r",stdin);
    static int w[3500],v[3500],dp[3500][13000];
    int N,M,i,j;
    while(cin>>N>>M){
        memset(dp,0,sizeof(dp));
        for(i=1;i<=N;i++)
            cin>>w[i]>>v[i];
        for(i=1;i<=N;i++){//前i个物品
            for(j=0;j<=M;j++){//j=1 wrong
                if(w[i]>j)
                    dp[i][j]=dp[i-1][j];//载重扩大的不够,还不可能存任何东西,与上一个载重一样
                else
                    dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
            }
        }
        cout<<dp[N][M]<<endl;//填表完成,最后一个就是答案了
    }
    return 0;
}
/*--------------------------WA||RE------------------------*/

滚动数组AC

#include <iostream>
#include <cstdio>
#include <string.h>
#include <algorithm>
using namespace std;
int main()
{
//  freopen("C:\\Users\\Admin\\Desktop\\input.txt","r",stdin);
    static int w[3403],v[3403],dp[2][13000];
    int N,M,i,j;
    while(cin>>N>>M){
        memset(dp,0,sizeof(dp));
        for(i=1;i<=N;i++)
            cin>>w[i]>>v[i];
        for(i=1;i<=N;i++){//前i个物品
            for(j=0;j<=M;j++){//j=1 wrong
                if(w[i]>j)
                    dp[i%2][j]=dp[(i-1)%2][j];//载重扩大的不够,还不可能存任何东西,与上一个载重一样
                else
                    dp[i%2][j]=max(dp[(i-1)%2][j], dp[(i-1)%2][j-w[i]]+v[i]);
            }
        }
        cout<<dp[(i-1)%2][M]<<endl;//填表完成,最后一个就是答案了
    }
    return 0;
}

一位数组:突然明白

先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1…N,每次算出来二维数组f[i][0…V]的所有值。那么,如果只用一个数组f[0…V],能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来,能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够得到f[i-1][v]和f[i-1][v-c[i]]的值呢?事实上,这要求在每次主循环中我们以v=V…0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。
原文:https://blog.csdn.net/tr990511/article/details/7595854

对于一维数组进行第i次循环开始时,数组保存的都是第i-1次的数据,因为状态方程要依靠的是i-1次的数据,如果从前往后,左边的数据会变成第i次的,而方程依靠的就是左边上一次的,所以只能从右边开始。

#include <iostream>
#include <cstdio>
#include <string.h>
#include <algorithm>
using namespace std;
int main()
{
//  freopen("C:\\Users\\Admin\\Desktop\\input.txt","r",stdin);
    static int w[3403],v[3403],dp[13000];//13000是重量
    int N,M,i,j;
    while(cin>>N>>M){
        memset(dp,0,sizeof(dp));
        for(i=1;i<=N;i++)
            cin>>w[i]>>v[i];
        for(i=1;i<=N;i++)
            for(j=M;j>=w[i];j--)//这里直接判断了是否超重,如果i的重量大于j(剩余载重),价值就不可能会发生变化
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
              //dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
        cout<<dp[M]<<endl;
    }
    return 0;
}

F - 最长公共子序列

A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = < x1, x2, …, xm > another sequence Z = < z1, z2, …, zk > is a subsequence of X if there exists a strictly increasing sequence < i1, i2, …, ik > of indices of X such that for all j = 1,2,…,k, x ij = zj. For example, Z = < a, b, f, c > is a subsequence of X = < a, b, c, f, b, c > with index sequence < 1, 2, 4, 6 >. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.
Input
The program input is from the std input. Each data set in the input contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct.
Output
For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line.
Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0

设X=x1x2…xm和Y=y1y2…yn是两个序列,Z=z1z2…zk是这两个序列的一个最长公共子序列。
1.如果xm=yn,那么zk=xm=yn,且Zk-1是Xm-1,Yn-1的一个最长公共子序列;
2.如果xm≠yn,那么zk≠xm,意味着Z是Xm-1,Y的一个最长公共子序列;
3.如果xm≠yn,那么zk≠yn,意味着Z是X,Yn-1的一个最长公共子序列。
原文:https://blog.csdn.net/u013921430/article/details/79299678

xm=yn: lcm[m,n]=lcm[m-1,n-1]+1
otherwise: lcm[m,n]=max {lcm[m,n-1], lcm[m-1,n]}

#include <iostream>
#include <string>
#include <string.h>
#include <algorithm>
#include <cstdio>
using namespace std;
int main()
{
//  freopen("C:\\Users\\Admin\\Desktop\\input.txt","r",stdin);
    int lcm[1000][1000];
    string a,b;
    while(cin>>a>>b){
        memset(lcm,0,sizeof(lcm));
        int la=a.size(),lb=b.size();
        int i,j;
        for(i=1;i<=la;i++)
            for(j=1;j<=lb;j++){
                if(a[i-1]==b[j-1])
                    lcm[i][j]=lcm[i-1][j-1]+1;
                else
                    lcm[i][j]=max(lcm[i][j-1],lcm[i-1][j]);
            }
        cout<<lcm[la][lb]<<endl;
    }
    return 0;
}

G - 最长下降子序列

http://acm.hdu.edu.cn/discuss/problem/post/reply.php?postid=29978&messageid=1&deep=0
最优问题dp,肯定优先考虑是否能累,因此按照长或宽排序。
dp[x]代表 拿前x个 拿到第x个能累多高,下面是方程(如果是前x个方程就是dp[i]由dp[i-1]推的了,明显不对,例如选了第i-1个,第i个的长宽和他一样,但明显更高,dp[i]仍然不会更新)。
dp[i]=max(dp[j]+block[i].h , dp[i]) 前者理解为在1–i-1中选一个再加自己,后者理解为前面有两个大小相同的,第一次dp加上了第一块,第二次dp用加上第2块的和加上第1块的进行比较。
每个方块都有6种情况,但只关心高的情况,可以假设长较长,因此只有三种情况了。
注意dp[3n]不一定是最大的。

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cmath>
#include <cstdio>
using namespace std;
struct block
{
    int x,y,z;//x>y
};
int cmp(block a, block b)
{
    return a.x>b.x;
}
int main()
{
    block ls[100];
    int n,dp[100];
    int cnt=0;
    while(cin>>n&&n){
        memset(ls,0,sizeof(ls));
        int i,j;
        for(i=1;i<=n;i++){
            int a,b,c;
            cin>>a>>b>>c;
            ls[3*i-2].z=a,ls[3*i-2].x=max(b,c),ls[3*i-2].y=min(b,c);
            ls[3*i-1].z=b,ls[3*i-1].x=max(a,c),ls[3*i-1].y=min(a,c);
            ls[3*i].z=c,ls[3*i].x=max(b,a),ls[3*i].y=min(b,a);
        }
        sort(ls+1,ls+3*n+1,cmp);
        for(i=1;i<=3*n;i++)dp[i]=ls[i].z;
        for(i=1;i<=3*n;i++){
            for(j=1;j<i;j++){
                if(ls[j].x>ls[i].x&&ls[j].y>ls[i].y)
                    dp[i]=max(dp[j]+ls[i].z, dp[i]);
            }
        }
        int ans=0;
        for(int i=1;i<=3*n;i++)
            ans=max(ans , dp[i]);
        printf("Case %d: maximum height = %d\n",++cnt,ans);//dp[3*n]不一定是最大的啊
    }
    return 0;
}

H - 记忆化搜索

一开始在主函数里枚举一次,然而不行,对于12345/98765中的9状态应该是从右边8得到
因此要重新创个函数进行递归

#include <iostream>
#include <algorithm>
#include <string.h>
#include <cstdio>
using namespace std;
int vis[109][109];
int m,n;
int in(int x, int y)
{
    if(x>0&&x<m+1&&y>0&&y<m+1)
        return 1;
    return 0;
}
int dp(int pic[109][109], int i, int j, int v[109][109])
{
    if(vis[i][j]){//如果这个点已经被访问过了
        return v[i][j];
    }
    vis[i][j]=1;
    int now=pic[i][j],up=pic[i-1][j],right=pic[i][j+1],down=pic[i+1][j],left=pic[i][j-1];
    if(in(i-1,j)&&now>up){
        //cout<<"("<<i<<","<<j<<")"<<"up"<<endl;
        v[i][j]=max(v[i][j],dp(pic,i-1,j,v)+1);
    }
    if(in(i,j+1)&&now>right){
        //cout<<"("<<i<<","<<j<<")"<<"right"<<endl;
        v[i][j]=max(v[i][j],dp(pic,i,j+1,v)+1);
    }
    if(in(i+1,j)&&now>down){
        //cout<<"("<<i<<","<<j<<")"<<"down"<<endl;
        v[i][j]=max(v[i][j],dp(pic,i+1,j,v)+1);
    }
    if(in(i,j-1)&&now>left){
        //cout<<"("<<i<<","<<j<<")"<<"left"<<endl;
        v[i][j]=max(v[i][j],dp(pic,i,j-1,v)+1);
    }
    return v[i][j];
}
int main()
{
//    freopen("C:\\Users\\Administrator\\Desktop\\input.txt","r",stdin);
    int pic[109][109],v[109][109];
    memset(pic,0,sizeof(pic));
    memset(v,0,sizeof(v));
    cin>>m>>n;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            cin>>pic[i][j];
    int ans=0;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++){
            dp(pic,i,j,v);
            ans=max(ans,v[i][j]);
        }
    cout<<ans+1;
    return 0;
}

I - trie树

前缀树/字典树
树是从一个节点伸出来的,因此每次添加新节点的时候最好new一下
一开始把root写成了野指针,应该先把root设置为node,再用指针指向root

#include <cstdio>
#include <cstring>
using namespace std;
struct node
{
    node *next[26];
    int val;
};
node root;//node *root返回错误,这是个野指针肯定用不成啊
void make(char *str)
{
    int len=strlen(str);
    int i;
    node *t=&root;
    for(i=0;i<len;i++){
        int id=str[i]-'a';
        if(t->next[id]==NULL){
            node *p = new node;//创建一个新node
            p->val=1;
            for(int i=0;i<26;i++)p->next[i]=NULL;
            t->next[id]=p;
        }
        else{
            t->next[id]->val++;
        }
        t=t->next[id];
    }
}
int find(char *str)
{
    int len=strlen(str),i,id;
    node *p=&root;
    for(i=0;i<len;i++){
        id=str[i]-'a';
        if(p->next[id]==NULL)
            return 0;
        p=p->next[id];
    }
    return p->val;
}
int main()
{
    for(int i=0;i<26;i++)root.next[i]=NULL;
    char str[30];
    while(1){
        gets(str);
        if(str[0]=='\0')
            break;
        make(str);
    }
    while(scanf("%s",str)!=EOF){
        printf("%d\n",find(str));
    }
    return 0;
}

J - trie树

next中只用两个指针
一开始root在main内定义,写函数的时候忘记加引用,一直不会修改root下的值

#include <cstdio>
#include <cstring>
using namespace std;
struct node
{
    node *next[2];
    int val;
    int flag;//表明是否是是最后一个字符
};
        /*else
        {
            int len=strlen(ls[i]);
            int j;
            node *t=&root;
            for(j=0;j<len;j++){
            int id=ls[i][j]-'0';
            if(t->next[id]==NULL){
                node *p = new node;//创建一个新node
                p->val=1,p->flag=0;
                for(int j=0;j<2;j++)p->next[j]=NULL;
                t->next[id]=p;
            }
            else{
                t->next[id]->val++;
            }
            t=t->next[id];
            printf("%d %d\n",t->val,t->flag);
    }
        t->flag++;
        }
        i++;
    }*/
void make(char *str,node &root)//void make(char *str,node root)
{
    int len=strlen(str);
    int i,j;
    node *t=&root;
    for(i=0;i<len;i++){
        int id=str[i]-'0';
        //printf("id is %d;",id);
        //printf("t->next[id]=NULL? is %d;",t->next[id]==NULL);
        if(t->next[id]==NULL){
            node *p = new node;//创建一个新node
            p->val=1,p->flag=0;
            for(j=0;j<2;j++)p->next[j]=NULL;
            t->next[id]=p;
        }
        else{
            //printf("do it;");
            t->next[id]->val++;
        }
        t=t->next[id];
        if(i==len-1)t->flag++;
        //printf("%d %d %d\n",i,t->val,t->flag);
    }
}
int find(char *str,node root)
{
   // printf("find:\n");
    int len=strlen(str),i,id;
   // printf("str:%s len:%d",str,len);
    node *p=&root;
    for(i=0;i<len;i++){
        //printf("    i:   %d\n",i);
        id=str[i]-'0';
        if(i<len&&p->flag>0){
            //printf("    i<len&&p->flag>0    :");
           // printf("p->flag:%d\n",p->flag);
            return 0;
        }
        if(i==len&&p->flag>1){
            //printf("    i==len&&p->flag>1    :");
            //printf("p->flag:%d\n",p->flag);
            return 0;
        }
        //printf("%d ",p->val);
        p=p->next[id];
    }
    return 1;
}
int main()
{
    node root;//node *root返回错误,这是个野指针肯定用不成啊
    root.val=999,root.flag=0;
    for(int i=0;i<2;i++)root.next[i]=NULL;
    char ls[10][30];
    int cnt=0,i=0;
    while(~scanf("%s",ls[i])){
        if(ls[i][0]=='9'){
            int j,ans=1;
            for(j=0;j<i;j++){
                ans = ans & find(ls[j],root);
            }
            if(ans)
                printf("Set %d is immediately decodable\n",++cnt);
            else
                printf("Set %d is not immediately decodable\n",++cnt);
            i=0;
            root.next[0]=NULL,root.next[1]=NULL;
            continue;
        }
        make(ls[i],root);
        //i++;
        /*else
            {
            char str[30];
            strcpy(str,ls[i]);
    int len=strlen(str);
    int i,j;
    node *t=&root;
    for(i=0;i<len;i++){
        int id=str[i]-'0';
        if(t->next[id]==NULL){
            node *p = new node;//创建一个新node
            p->val=1,p->flag=0;
            for(j=0;j<2;j++)p->next[j]=NULL;
            t->next[id]=p;
        }
        else{
            printf("%d  ",999);
            t->next[id]->val++;
        }
        t=t->next[id];
        if(i==len-1)t->flag++;
        printf("%d %d %d\n",i,t->val,t->flag);
    }
        }*/
        i++;
    }
    return 0;
}

K - 并查集

并查集数组实现看几个根节点
这里用join而不是pre[b]=a,因为遇到1 2,2 1这种环会不断更新
根节点选择自己的值就好,而不是-1

#include <cstdio>
#include <iostream>
#include <string.h>
using namespace std;
int find_root(int x, int parent[])
{
    int x_root=x;
    if(parent[x_root]==x)return x_root;
    else find_root(parent[x_root],parent);
}
int main()
{
    int parent[1009];
    int t,m=0;
    cin>>t;
    while(t--){
        m=0;
        int a,b,n;
        scanf("%d%d",&m,&n);
        for(int i=1;i<=m;i++)
            parent[i]=i;
        for(int i=0;i<n;i++){
            scanf("%d%d",&a,&b);
            int ra=find_root(a,parent),rb=find_root(b,parent);
            if(ra!=rb)parent[rb]=ra;
        }
        int cnt=0;
        for(int i=1;i<=m;i++){
            if(parent[i]==i)
                cnt++;
        }
        printf("%d\n",cnt);
    }
    return 0;
}

L - 并查集 (find函数写法不同会WA?)

正常并查集,但是自己写的递归find_root有问题,很奇怪,K题这么写findroot都可以过的
递归的话5<-4<-3,parent[3]=4,parent[4]=5,但用循环都是5,有可能在递归的时候爆掉了

#include <iostream>
#include <string.h>
using namespace std;
int find_root(int x, int parent[]);
void join(int a, int b, int parent[])
{
    int ra=find_root(a,parent),rb=find_root(b,parent);
    if(ra!=rb){
        parent[ra]=rb;
    }
}

int find_root(int x,int pre[])
{
	int r=x;
	while(pre[r]!=r)
	r=pre[r];

	int i=x;
	int j;
	while(i!=j)
	{
		j=pre[i];
		pre[i]=r;
		i=j;
	}
	return r;

}

int find_roott(int x, int parent[])
{
    int x_root=parent[x];
    if(x==x_root)return x_root;
    else find_root(x_root,parent);
}
int main()
{
    int a[35000];
    int parent[35000];
    int n,m;
    while(cin>>n>>m&&n){
        if(m==0){
            cout<<1<<endl;
            continue;
        }
        for(int i=0;i<n;i++)
            parent[i]=i;
        for(int i=0;i<m;i++){
            int k;
            cin>>k;
            int first;
            for(int j=0;j<k;j++){
                cin>>a[j];
                join(a[0],a[j],parent);
            }
        }
        int ans=0;
        for(int i=0;i<n;i++){
            int t=find_root(i,parent);
            if(t==find_root(0,parent))
                ++ans;
        }
        cout<<ans<<endl;
    }
    return 0;
}

M - 并查集

第一次join写错了所以WA
主要思想是在修复好一个后全部尝试一遍,看已经修复的点能不能连在一起

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
int dx[1009],dy[1009],parent[1009],repair[1009];
double Dis(int a,int b)
{
    return sqrt( double(dx[a]-dx[b])*(dx[a]-dx[b]) + (dy[a]-dy[b])*(dy[a]-dy[b]) );
}
int find(int x)
{
    int r=x;
    while(r!=parent[r])
        r=parent[r];
    return r;
}
void join(int a, int b)
{
    int ra=find(a),rb=find(b);
    if(ra!=rb)
        parent[rb]=ra;//parent[b]=a;
}
int main()
{
    int n,d;
    cin>>n>>d;
    for(int i=1;i<=n;i++){
        parent[i]=i;
        cin>>dx[i]>>dy[i];
    }
    char c;
    int flag=1;//一个都没修
    int first;
    while(cin>>c){
        if(c=='O'){
            if(flag){
                cin>>first;
                flag=0;
                repair[first]=1;
                continue;
            }
            int a;
            cin>>a;
            repair[a]=1;
            for(int i=1;i<=n;i++)
                if(Dis(a,i)<=(double)d&&repair[i])
                    join(a,i);
        }
        if(c=='S'){
            int a,b;
            cin>>a>>b;
            if(find(a)==find(b))
                cout<<"SUCCESS"<<endl;
            else
                cout<<"FAIL"<<endl;
        }
    }
    return 0;
}

N - 并查集

和how many tables一样
一开始数组开小了RE

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
int parent[55000];
int find(int x)
{
    int r=x;
    while(r!=parent[r])
        r=parent[r];
    //优化
    int i,j;
    i=x,j=parent[i];
    while(i!=j){
        parent[i]=r;
        i=j;
        j=parent[i];
    }
    return r;
}
void join(int a, int b)
{
    int ra=find(a),rb=find(b);
    if(ra!=rb)
        parent[rb]=ra;//parent[b]=a;
}
int main()
{
    int n,m;
    int c=0;
    while(scanf("%d%d",&n,&m)&&n){
        for(int i=1;i<=n;i++)
            parent[i]=i;
        int a,b;
        for(int i=0;i<m;i++){
            scanf("%d%d",&a,&b);
            join(a,b);
        }
        int cnt=0;
        for(int i=1;i<=n;i++){
            if(parent[i]==i)
                cnt++;
        }
        printf("Case %d: %d\n",++c,cnt);
    }
    return 0;
}

O - 并查集 (不会)

https://blog.csdn.net/m0_37579232/article/details/79920785两种解法链接


P - Play on Words

欧拉回路,一笔画问题。
充分条件第一点就是连通。
对于无向图,除了起点和终点,其他点的度数应该为偶数。
对于有向图,最多两个点的入度不等于出度,而且一个的入度比出度大1(终点),另一个出度比入度大1(起点)。
对于 aa,bb,这种数据,并没有连通,但a,b的入度出度都是偶数,因此要用并查集来判断是否连通。
ans=0;
if(vis[i] && find(a)==a)表明有a这个点但是a的根仍是a,ans+++;
如果ans=1说明可能是欧拉回路ab,bc,ca;或欧拉通路ab,bc
如果ans>1说明不通aa,bb
ans会等于0吗?(自己觉得不可能)
如果每个点入度等于出度—>欧拉回路
只有两个点入度出度不等并且一个入度比另一个大1,他出度比另一个小一---->欧拉通路

#include <iostream>
#include <string>
#include <string.h>
using namespace std;
int find(int parent[], int x)
{
    if(x==parent[x])
        return x;
    find(parent,parent[x]);
}
void join(int parent[], int a, int b)
{
    int ra=find(parent,a),rb=find(parent,b);
    if(ra!=rb)
        parent[rb]=ra;
}
int main()
{
    int in[27],out[27],parent[27],vis[27];
    int T;
    cin>>T;
    while(T--){
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=26;i++)parent[i]=i;
        int n;
        cin>>n;
        for(int i=1;i<=n;i++){
            string str;
            cin>>str;
            int a,b;
            a=str[0]-'a'+1;
            b=str[str.length()-1]-'a'+1;
            vis[a]=vis[b]=1;
            out[a]++;
            in[b]++;
            join(parent,a,b);
        }
        //接下来判读是否连通 条件1
        int cnt=0;
        for(int i=1;i<=26;i++){
            if(vis[i] && parent[i]==i){
                cnt++;
                if(cnt>1)break;
            }
        }
        if(cnt>1){
            cout<<"The door cannot be opened."<<endl;
            continue;
        }
        //下俩判断欧拉 条件2
        int pos[2],ans=0;
        for(int i=1;i<=26;i++){
            if(out[i]!=in[i]){
                pos[ans++]=i;
            }
        }
        if(ans==0)//欧拉回路
            cout<<"Ordering is possible."<<endl;
        else if(ans==2 && ((in[pos[0]]-out[pos[0]]==1 && out[pos[1]]-in[pos[1]]==1) || (out[pos[0]]-in[pos[0]]==1 && in[pos[1]]-out[pos[1]]==1)))
            cout<<"Ordering is possible."<<endl;
        else
            cout<<"The door cannot be opened."<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值