[Codeforces #296 (Div. 2)]简要题解

A. Playing with Paper

题目大意

给一个大小为 ab 的长方形纸片反复做如下操作:
这里写图片描述
直到纸片变成了一个正方形,问这样做最终得到了多少个正方形(最后剩下的那个正方形也算)。

题解

比较好想到一个递归的做法,就是每次模拟把 ab 大小的棋盘变成 b(ab) 大小的棋盘,但是这样做会爆栈,原因是中间递归的次数太多,但是可以发现如果这个纸片是一个很长的长方形的话,多次操作得到的正方形都是边长为 b 的,这样的操作次数是[ab]次,因此改改代码就能AC了。

代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>

using namespace std;

typedef long long int LL;

LL calc(LL a,LL b)
{
    if(!a||!b) return 0;
    if(a==b) return 1;
    if(a<b) swap(a,b);
    return calc(b,a%b)+a/b;
}

int main()
{
    LL a,b;
    scanf("%I64d%I64d",&a,&b);
    printf("%I64d\n",calc(a,b));
    return 0;
}

B - Error Correct System

题目大意

给出两个字符串 ST ,定义它们的距离是它们同一位上字母不相同的位数和,要你选择 S 上的两个位置的字母并交换它们,求交换后两个字符串的最小距离是多少。

题解

结果无非是三种情况:
1、距离减少2,即交换两个位置的字母后,正好让被交换的两个位置对应起来了,如S:abtc, T: batc,交换了 S 的a和b后,距离由2减少为0
2、距离减少1,如S:abtc, T: bxtc,交换了 S 的a和b后,距离由2减少为1
3、距离无法减少

于是可以用一个数组pos[a][b]表示同一位置上 S 中出现字母a T 中出现字母b的位置下标是多少,然后暴力扫一遍就行了。
注意细节情况,此题数据多,细节多,很容易WA

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <map>

#define MAXN 210000

using namespace std;

int pos[400][400],has[400]; //has[i]=字母i在T串中的位置
char S[MAXN],T[MAXN];
int dist=0;

int main()
{
    int n;
    scanf("%d",&n);
    scanf("%s",S+1);
    scanf("%s",T+1);
    for(int i=1;i<=n;i++)
        dist+=S[i]!=T[i];
    if(dist<=1) //!!!!
    {
        printf("%d\n-1 -1\n",dist);
        return 0;
    }
    for(int i=1;i<=n;i++)
        if(S[i]!=T[i])
            pos[(int)S[i]][(int)T[i]]=i,has[(int)S[i]]=i; //fuck!!!!
    for(char a='a';a<='z';a++) //注意先考虑距离减2再考虑距离-1的情况
        for(char b='a';b<='z';b++)
        {
            if(pos[(int)a][(int)b]&&pos[(int)b][(int)a])
            {
                printf("%d\n%d %d\n",dist-2,pos[(int)a][(int)b],pos[(int)b][(int)a]);
                return 0;
            }
        }
    for(char a='a';a<='z';a++)
        for(char b='a';b<='z';b++)
        {
            if(pos[(int)a][(int)b]&&has[(int)b])
            {
                printf("%d\n%d %d\n",dist-1,pos[(int)a][(int)b],has[(int)b]);
                return 0;
            }
        }
    printf("%d\n-1 -1\n",dist);
    return 0;
}

C. Glass Carving

题目大意

给一个初始大小为 WH 的玻璃,要对玻璃切 n 刀,每一刀的刀痕要么平行于x轴,要么平行于 y 轴,求每次切完一刀后玻璃被分割成的若干个小矩形里面积最大的矩形面积。

题解

STL题。
用两个set维护x轴和 y 轴方向的切割线坐标,两个multiset维护x y 方向的被切割的一段一段的区间长度,每次放入一个切割线,就能通过set快速找到被这个切割线所分割的相邻两个切割线坐标,以及被分割的那个区间的长度,将新的切割线插入set中,并删去multiset中老的那个区间,将一分为二后的两个新区间插入multiset中,每次得到的最大矩形的面积就是两个方向上的set的最大元素之积。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <set>

using namespace std;

typedef long long int LL;

set<int>sx,sy; //保存x方向和y方向的分割线
set<int>::iterator l,r; //当前分割线分割开的那个部件的两个边界线
multiset<int>lenx,leny; //两个方向上相邻分割线所夹区间的长度

int w,h,n;

int main()
{
    scanf("%d%d%d",&w,&h,&n);
    sx.insert(0),sx.insert(w);
    sy.insert(0),sy.insert(h);
    lenx.insert(w),leny.insert(h);
    for(int i=1;i<=n;i++)
    {
        char cmd[10];
        int x;
        scanf("%s",cmd);
        scanf("%d",&x);
        if(cmd[0]=='H')
        {
            r=sy.lower_bound(x);
            l=r;
            l--;
            sy.insert(x);
            leny.insert((*r)-x); //加入分割后得到的两个新区间
            leny.insert(x-(*l));
            leny.erase(leny.find((*r)-(*l))); //把被分割的那个区间删除
        }
        else
        {
            r=sx.lower_bound(x);
            l=r;
            l--;
            sx.insert(x);
            lenx.insert((*r)-x); //加入分割后得到的两个新区间
            lenx.insert(x-(*l));
            lenx.erase(lenx.find((*r)-(*l))); //把被分割的那个区间删除
        }
        printf("%I64d\n",(LL)(*lenx.rbegin())*(LL)(*leny.rbegin()));
    }
    return 0;
}

D.Clique Problem

题目大意

给出n个在坐标轴上的点,每个点的坐标 xi 以及一个参数 wi ,两个点间可以连边当且仅当 xixj>=wi+wj ,求建好的图的最大团。

思路

题目说得非常坑爹,而且还扯什么NP问题汗= =b。其实题目非常简单,可以转化为坐标轴上 n 个区间,每个区间是[xiwi,xi+wi],求最大的两两区间没有相交(端点相交不算相交)的区间集合大小。

实际上非常搞笑,这是个很简单的贪心问题,只需要对所有区间按照 xi+wi 升序排序即可(就是对区间右端点升序排序),然后从左到右扫一遍所有区间,用一个指针维护当前扫过的最右边的端点坐标 maxr ,若新的区间的左端点 >=maxr ,则将这个区间加入集合,最终便能得到答案。

虽然此题很傻逼,但是我还是打错了一个大于号(打成了小于号),WA了一发,我是傻叉。。。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 210000

using namespace std;

struct Circle
{
    int x,r;
}circles[MAXN];

bool cmp(Circle a,Circle b)
{
    return a.x+a.r<b.x+b.r;
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&circles[i].x,&circles[i].r);
    sort(circles+1,circles+n+1,cmp);
    int maxr=circles[1].x+circles[1].r,ans=1;
    for(int i=2;i<=n;i++)
    {
        if(circles[i].x-circles[i].r>=maxr)
        {
            ans++;
            maxr=circles[i].x+circles[i].r; //!!!!!!!
        }
    }
    printf("%d\n",ans);
    return 0;
}

E.Data Center Drama

题目大意

给出 n m边的无向图,要你给这些边定一个方向,并加上尽量少的边,使得这个无向图变成一个有向图,并且是整个有向图是一个欧拉回路。

题解

非常不错的一道图论的构造题。
注意到最终的图是一个欧拉回路,那么每个点最终的入度等于出度,如果这个欧拉回路长是偶数的话,就相当于把这个图变成无向图(所有边加上反向边),并且新图的每个点的度数均为偶数。因此我们就需要在原图中,对于所有度数为奇数的点,依次连无向边,这样原图就变成了所有点度数均为偶数了,然后再对这个无向图,随便找个起点做欧拉回路,得到这个欧拉回路的路径,要把这个无向图的欧拉回路变成一个有向图的欧拉回路就很容易了,假如原来无向图的欧拉回路是a-b-c-d-e这样的形式,只需要把它变成a->b<-c->d<-e……这样的形式,就是给原来的无向图的每条边确定了方向,这样做把每个点在无向图中的偶数的度数均匀地分成了一半出度、一半入度,构造出的新有向图就是一个欧拉回路 了,但是前提就是这个欧拉回路的长度为偶数,这是很显然的。
那么如果把奇数度数点依次加完边后无向边总数为奇数怎么办?随便找个点加自环即可。

但是此题还有很重要的一点需要注意:做欧拉回路时,如果不做优化,把之前访问过的废边都访问的话,整个程序的复杂度就是 degree[i]2 ,会TLE,因此要在做欧拉回路时,每访问完一条边,就把这条边删掉,最好实现的方式就是用multiset当作邻接表存边了(因为有自环所以不能用set),另外自环的边也是要加两遍的。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>
#include <set>

#define MAXV 600000
#define MAXE 1600000

using namespace std;

vector<int>odd;
vector<int>ans;
multiset<int>G[MAXV]; //因为有自环的边所以要用多重集!!!

int nCount=0,tot_edge=0; //tot_edge=加入的不同的边的总个数
int degree[MAXV]; //度

inline void AddEdge(const int &U,const int &V,const int &I)
{
    G[U].insert(V);
}

void DFS(int u)
{
    while(!G[u].empty())
    {
        int v=*G[u].begin();
        //cout<<u<<' '<<v<<endl;
        G[u].erase(G[u].begin());
        G[v].erase(G[v].find(u));
        DFS(v);
    }
    ans.push_back(u);
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        degree[u]++,degree[v]++;
        AddEdge(u,v,++tot_edge);
        AddEdge(v,u,tot_edge);
    }
    for(int i=1;i<=n;i++)
        if(degree[i]&1)
            odd.push_back(i);
    for(int i=0;i<(int)odd.size();i+=2)
    {
        int u=odd[i],v=odd[i+1];
        AddEdge(u,v,++tot_edge);
        AddEdge(v,u,tot_edge);
    }
    if(tot_edge&1) //只有奇数条边,那么需要加一条自环的边
        AddEdge(1,1,++tot_edge),AddEdge(1,1,tot_edge); //加两遍!!!!
    DFS(1);
    printf("%d\n",tot_edge);
    for(int i=0;i<(int)ans.size()-1;i++)
    {
        if(i&1)
            printf("%d %d\n",ans[i],ans[i+1]);
        else
            printf("%d %d\n",ans[i+1],ans[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值