CUGB20级算法课期末考试 (ACM训练) 整理

1.N只老虎爱跳舞

  • 题意分析:n个老虎,有m个关系,问有多少个老虎能确定排名?
  • 解法:对每只老虎都分别向前向后搜索并返回数量,如果数量为n - 1,说明这个老虎相对于其他所有老虎的相等关系都知道,也就知道了它的排名了
  • 代码:
    #include
    using namespace std;
    const int MAXN = 101;
    vector in[MAXN];
    vector out[MAXN];
    
    int fin(int i)
    {
        bool use[MAXN] = {0};
        int sum = 0;
        queue qu;
        qu.push(i);
        while(!qu.empty())
        {
            int x = qu.front();qu.pop();
            int end = in[x].size();
            for(int j = 0;j < end;j++)
                if(use[in[x][j]] == 0)
                {
                    use[in[x][j]] = 1;
                    qu.push(in[x][j]);
                    sum++;
                }
        }
        return sum;
    }
    
    int fout(int i)
    {
        bool use[MAXN] = {0};
        int sum = 0;
        queue qu;
        qu.push(i);
        while(!qu.empty())
        {
            int x = qu.front();qu.pop();
            int end = out[x].size();
            for(int j = 0;j < end;j++)
                if(use[out[x][j]] == 0)
                {
                    use[out[x][j]] = 1;
                    qu.push(out[x][j]);
                    sum++;
                }
        }
        return sum;
    }
    
    
    int main()
    {
        int n,m;cin >> n >> m;
        for(int i = 1;i <= m;i++)
        {
            int x,y;cin >> x >> y;
            in[x].push_back(y);
            out[y].push_back(x);
        }
        int sum = 0;
        for(int i = 1;i <= n;i++)
        {
            if(fin(i) + fout(i) == n - 1)
                sum++;
        }
        cout << sum;
    }

2.掩体计划

  • 思路:将所有节点相连的最小花费即最小生成树问题(但在我写的时候我还不知道,紧急恶补了一下)
  • 最小生成树的解法一般有两种:Kruskal和Prim
  • (粗略地说)Kruskal算法为选最短的边,如果这两个点已经在一个集合里的话就舍弃,否则整合这两个集合并取这个边
  • (粗略地说)Prim算法,随机挑选一个初始点作为已连接的点的集合,找最短的端点一个在集合内,一个在外的边,取这个边并且将那个在外的端点加入集合
  • Kruskal代码:
    #include
    using namespace std;
    const int MAXN = 5e3 + 10,MAXM = 1e4 + 10;
    
    int n;
    
    int ptop = 1;
    struct link{
    	int x,y,v;
    }li[MAXM];
    void add(int i,int j,int v)
    {
    	li[ptop++] = {i,j,v};
    }
    
    bool cmp(link a,link b)
    {
    	return a.v < b.v;
    }
    
    int dad[MAXN];
    void pre()
    {
    	for(int i = 1;i <= n;i++)
    	dad[i] = i;
    }
    int find(int x)
    {
    	if(x == dad[x])
    	return x;
    	else
    	return dad[x] = find(dad[x]);
    }
    
    int main()
    {
    	int m;cin >> n >> m;
    	pre();
    	for(int i = 1;i <= m;i++)
    	{
    		int x,y,w;cin >> x >> y >> w;
    		add(x,y,w);
    	}
    	sort(li + 1,li + m + 1,cmp);
    	int sum = 0;
    	for(int i = 1;i <= m;i++)
    	{
    		int x = li[i].x,y = li[i].y,w = li[i].v;
    		if(find(x) != find(y))
    		{
    			dad[find(y)] = find(x);
    			sum += w;
    		}
    	}
    	int t = 1;
    	while(find(t) == find(t + 1))
    	{
    		t++;
    		if(t == n - 1)
    		{
    			cout << sum;
    			return 0;
    		}
    	}
    	cout << "Ridiculous! We can not combine city like a LOGO!!!";
    }

3.你为什么不直接打死他?

  • 应该是这里面最难的题了,虽然将思路写出来就感觉不怎么难了
  • 思路:龙有三个状态,2血,1血,0血,用三进制来表示多个龙的状态,即状压dp,当然也可以加上滚动数组。
  • 代码:
    #include
    using namespace std;
    int n,all,sum,l,be;
    double dp[60000][32];
    int pow3[11];
    int turn(int x)
    {
        int sum = 0;
        while(x != 0)
        {
            if(x % 3 != 0)
                sum++;
            x /= 3;
        }
        return sum;
    }
    
    void slove(int aim,int att,int sum)
    {
    	dp[aim][att + 1] += dp[aim][att] / (sum + 1.0);
    	int t = 0;
    	int aa = aim;
    	for(int i = 1;i <= n;i++)
    	{
    		if(aa % 3 != 0)
    		dp[aim - pow3[t]][att + 1] += dp[aim][att] / (sum + 1.0);
    		t++;
    		aa /= 3;
    	}
    }
    
    int main()
    {
        int m;cin >> n >> m;
        be = 0;
        pow3[0] = 1;
        for(int i = 1;i <= n;i++)
        {
            pow3[i] = pow3[i - 1] * 3;
        }
        for(int i = 1;i <= n;i++)
        {
            be *= 3;
            be += 2;
        }
        dp[be][0] = 1;
        for(int i = 0;i <= 3 * m;i++)
        {
            for(int j = 0;j <= be;j++)
            {
                int sum = turn(j);//剩下的人数
                slove(j,i,sum);
            }
        }
        cout << dp[0][3 * m];
    }

4.高维世界

  • 暴力,没有一点感情(记得开long long)
  • 代码:
    #include
    using namespace std;
    long long a[5001][11];
    int main()
    {
        int n,k;cin >> n >> k;
        long long mi = -1;
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= k;j++)
                cin >> a[i][j];
            for(int j = 1;j < i;j++)
            {
                long long sum = 0;
                for(int kk = 1;kk <= k;kk++)
                    sum += (a[i][kk] - a[j][kk]) * (a[i][kk] - a[j][kk]);
                if(sum < mi || mi == -1)
                    mi = sum;
            }
        }
        cout << mi;
    }

5.牛牛坤卖手镯

  • 经典最大子序列dp[i] = max(dp[i - 1] + a[i],a[i])
  • 代码:
    #include
    using namespace std;
    long long a[5001][11];
    int main()
    {
        int n,k;cin >> n >> k;
        long long mi = -1;
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= k;j++)
                cin >> a[i][j];
            for(int j = 1;j < i;j++)
            {
                long long sum = 0;
                for(int kk = 1;kk <= k;kk++)
                    sum += (a[i][kk] - a[j][kk]) * (a[i][kk] - a[j][kk]);
                if(sum < mi || mi == -1)
                    mi = sum;
            }
        }
        cout << mi;
    }

6.不废话了

  • 数学题,看着很吓人
  • 思路:首先 a & b <= min(a,b),然后⌊(a + b) / 2⌋ >= min(a,b),所以a & b == min(a,b) == ⌊(a + b) / 2⌋,根据后面的式子,假设a <= b,那么b <= a + 1,因为如果b >= a + 2,那么(a + b) / 2 > a,就不满足后式,然后又根据前式,a & b == a,说明a有的二进制位b都要有,如果b == a + 1,b又不进位,那么a只能是偶数。
  • 这样一分,我们就让x / 2相等的作一堆来计算数量
  • 代码:
    #include
    using namespace std;
    const int MAXN = 1e5 + 10;
    long long a[MAXN];
    //a & b <= min(a,b)?
    //(a + b) / 2 >= min(a,b)?
    //a == b == c?
    //.^.
    //偶数与偶数加1为一组
    map mp;
    long long turn(long long x)
    {
        return x * (x - 1) / 2 * (x - 2) / 3;
    }
    int main()
    {
        int n,m;cin >> n >> m;
        long long sum = 0;
        for(int i = 1;i <= n;i++)
        {
            cin >> a[i];
            a[i] /= 2;
            mp[a[i]]++;
            if(mp[a[i]] >= 3)
                sum += turn(mp[a[i]]) - turn(mp[a[i]] - 1);
        }
        for(int i = 1;i <= m;i++)
        {
            int x,y;cin >> x >> y;
            y /= 2;
            if(mp[a[x]] >= 3)
                sum -= turn(mp[a[x]]) - turn(mp[a[x]] - 1);
            mp[a[x]]--;
            a[x] = y;
            if(++mp[y] >= 3)
                sum += turn(mp[y]) - turn(mp[y] - 1);
            cout << sum << endl;
        }
    }

7.阶梯计划

  • 首先一看,将必须品取出后0/1背包找最高价值,但是容量范围过大,又一看价值的范围比较小,那么我们就反过来想,对体积进行0/1背包找出对应价值下的最大剩余容量,如果最大剩余容量大于等于0那么说明这个价值可取,再取价值的最大值
  • 代码:
    #include
    using namespace std;
    const int MAXN = 1e3 + 10,MAXV = 1e4 + 10;
    int www[MAXN],v[MAXN];
    int dp[MAXV];
    bool able[MAXV] = {0};
    int main()
    {
    	int w,n;cin >> w >> n;
    	int nn = 0;
    	for(int i = 1;i <= n;i++)
    	{
    		int ww,vv;cin >> ww >> vv;
    		if(vv == -1)
    		{
    			w -= ww;
    		}
    		else
    		{
    			www[++nn] = ww;
    			v[nn] = vv;
    		}
    	}
    	dp[0] = w;
    	able[0] = 1;
    	int mv = 0;
    	int mvv = 0;
    	for(int j = 1;j <= nn;j++)
    	{
    		mv += v[j];
    		for(int i = min(MAXV - 1,mv);i >= v[j];i--)
    		{
    			if(able[i - v[j]])
    				if(dp[i] <= dp[i - v[j]] - www[j])
    				{
    					dp[i] = dp[i - v[j]] - www[j];
    					able[i] = 1;
    					mvv = max(i,mvv);
    				}
    		}
    	}
    	if(mvv == 0)
    	cout << "failed!";
    	else
    	cout << mvv;
    }

8.修复磁盘

  • 弹 弹 乐 ?
  • 思路:分为4种可能,直接能到,向左弹后能到,向右弹后能到,不能到,思路好想但公式比较难写出
  • 代码:
    #include
    using namespace std;
    int main()
    {
        int t;cin >> t;
        for(int i = 1;i <= t;i++)
        {
            int n,x,y,d;cin >> n >> x >> y >> d;
            if(abs(x - y) % d == 0)
                cout << abs(x - y) / d << endl;
            else
            {
                long long mi = -1;
                if((y - 1) % d == 0)
                mi = (y - 1) / d + 1 + (x - 1) / d;
                if((n - y) % d == 0)
                if(mi > (n - y) / d + 1 + (n - x) / d || mi == -1)
                mi = (n - y) / d + 1 + (n - x) / d;
                cout << mi << endl;
            }
        }
    }

9.鱼饵桶

  • 又是炉石
  • 分为两个,总和最大和单独最大,单独最大肯定是挑最大的再加最多的BUFF
  • 总和最大推一下公式就出来了(记得开long long)
  • 代码:
    #include
    using namespace std;
    int main()
    {
        long long n,m;cin >> n >> m;
        long long damage = 0,hp = 0,sumdamage = 0,sumhp = 0;
        for(int i = 1;i <= n;i++)
        {
            long long x,y;cin >> x >> y;
            if(x > damage)
                damage = x;
            if(y > hp)
                hp = y;
            sumdamage += x;
            sumhp += y;
        }
        long long ma = m * (1 + (n - 1) / 2) * (n - (n - 1) / 2);
        cout << sumdamage + ma << ' ' << sumhp + ma << ' ' << damage + n * m << ' ' << hp + n * m;
    }

10.口吃症

  • 代码:
    #include
    using namespace std;
    int main()
    {
        int n;cin >> n;
        int m;
        for(int i = 1;i <= 10;i++)
        {
            if((i + 1) * i / 2 == n)
                m = i;
        }
        int t = -1;
        string str;cin >> str;
        for(int i = 1;i <= m;i++)
        {
            t += i;
            cout << str[t];
        }
    }

11.等座位

  • 也挺水的,记录区间位置再二分即可
  • 代码:
    #include
    using namespace std;
    const int MAXN = 1e5 + 10;
    int st[MAXN];
    int main()
    {
        int n,m;cin >> n >> m;
        for(int i = 1;i <= n;i++)
        {
            int x;cin >> x;
            st[i] = st[i - 1] + x;
        }
        for(int i = 1;i <= m;i++)
        {
            int x;cin >> x;
            int begin = 1,end = n;
            int ans = 1;
            while(begin <= end)
            {
                int mid = (begin + end) / 2;
                if(st[mid] >= x)
                {
                    ans = mid;
                    end = mid - 1;
                }
                else
                    begin = mid + 1;
            }
            cout << ans << endl;
        }
    }

12.玩游戏

  • 最大公约数,辗转相除法,模拟,水
  • 代码:
    #include
    using namespace std;
    
    int gcd(int x,int y)
    {
        if(y == 0)
            return x;
        else if(x == 0)
            return y;
        return gcd(y,x % y);
    }
    
    int main()
    {
        int a,b,n;cin >> a >> b >> n;
        int type = 0;
        while(1)
        {
            if(gcd(a,n) <= n)
                n -= gcd(a,n);
            else
            {
                type = 1;
                break;
            }
            if(gcd(b,n) <= n)
                n -= gcd(b,n);
            else
            {
                type = 2;
                break;
            }
        }
        if(type == 1)
            cout << "xiao hua win!";
        else
            cout << "xiao peng win!";
    }

13.环球旅行

  • 就暴力搜索
  • 代码:
    #include
    using namespace std;
    //最小环,但是节点固定
    //搜索
    int n,num,be,mi;
    bool able[11];
    bool use[11];
    int l[11][11];
    void DFS(int x,int sum,int nn)
    {
        if(nn == num)
        {
            if(l[be][x] != 0)
                if(mi != -1)
                    mi = min(sum + l[be][x],mi);
                else
                    mi = sum + l[be][x];
        }
        else
        {
            for(int i = 1;i <= n;i++)
            if(able[i] && !use[i] && l[x][i] != 0)
            {
                use[i] = 1;
                DFS(i,sum + l[x][i],nn + 1);
                use[i] = 0;
            }
        }
    }
    void pre()
    {
        mi = -1;
        for(int i = 1;i <= n;i++)
            able[i] = use[i] = 0;
    }
    int main()
    {
        int m;cin >> n >> m;
        for(int i = 1;i <= m;i++)//录入
        {
            int x,y,t;cin >> x >> y >> t;
            if(l[x][y] == 0)
                l[x][y] = l[y][x] = t;
            else
                l[x][y] = l[y][x] = min(l[y][x],t);
        }
        int q;cin >> q;
        for(int i = 1;i <= q;i++)
        {
            pre();//初始化
            cin >> num;
            for(int j = 1;j <= num;j++)
            {
                cin >> be;able[be] = 1;//标记可以走
            }
            use[be] = 1;//标记起点
            DFS(be,0,1);
            cout << mi << endl;
        }
    }

你可能感兴趣的:(练习,算法,图论,c++)