Codeforces #292 Div 1 简要题解

A. Drazil and Factorial(516A)

题目链接

http://codeforces.com/contest/516/problem/A

题目大意

定义 F(a) 为数字 a 的每一位数字的阶乘之积,要你找出一个最大的数字x,使得 F(x)=F(a)

思路

可以找到下面的规律:
0! (无视掉)
1! (无视掉)
2!=2!
3!=3!
4!=2!2!3!
5!=5!
6!=5!3!
7!=7!
8!=7!2!2!2!
9!=7!3!3!2!

其中2 3 5 7均未变,显然在同样的F值下,用4 6 8 9不如拆成2 3 5 7来用更划算,因此最终的答案的每一位也显然只会出现2 3 5 7,因此我们可以对原来的 F(a) 分解质因数,然后先尽量用光 7! ,再尽量用光 5! ,依次类推,就可以得到最大的答案了

代码

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

using namespace std;

int n,fact[25],t[25];
char s[25];

void cal(int num,int val) //val=1是添加质因数,-1是减少质因数
{
    for(int x=2;x*x<=num;x++)
        while(num%x==0)
        {
            num/=x;
            t[x]+=val;
        }
    if(num>1) t[num]+=val;
}

int main()
{
    scanf("%d",&n);
    fact[1]=1;
    for(int i=2;i<=9;i++) fact[i]=fact[i-1]*i;
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
        cal(fact[s[i]-'0'],1);
    while(t[7]) cal(fact[7],-1),printf("7");
    while(t[5]) cal(fact[5],-1),printf("5");
    while(t[3]) cal(fact[3],-1),printf("3");
    while(t[2]) cal(fact[2],-1),printf("2");
    printf("\n");
    return 0;
}

B. Drazil and Tiles(516B)

题目链接

http://codeforces.com/contest/516/problem/B

题目大意

给一个带有障碍物的 nm 大小的棋盘放入若干个 12 大小的瓦片,问是否存在且仅存在一种使得整个棋盘被障碍物和瓦片完全覆盖,瓦片和障碍物、瓦片和瓦片均不重叠的方案,并输出这种方案。

思路

这个题目的做法真的非常有意思,值得学习
首先我们把每个空格当成一个结点,并求出每个空格的度数(相邻的空格个数),然后我们首先将初始时度数为1的点入队,每次从队首取出一个空格 a ,由于这个点一定是度数为1的,因此我们找出那个唯一的与它相邻的空格b,并在这两个空格上放上一个瓦片,标记这两个空格均访问过,更新与点 b 相邻的所有的空格c的度数-1,若找到某个 c 更新后度数为1,则再将这个c入队。
若最终的棋盘里仍有空格,那么表明无可行方案或有多种可行方案。

考虑两种不合法情况:
1、无可行方案。那么显然上面的做法无法覆盖掉所有的棋盘。
2、有多种方案。由于每次我们只选取度数为1的点 a 入队,这些点找到的相邻的空格b也是唯一的。若存在多种方案,则一定是在操作过程中,出现了某些度数大于1的点,使得它们无论如何也无法被加入到队列中,被瓦片覆盖。而假如我们对棋盘进行黑白染色,这些最终空着的格子可以构成若干个联通块,在每个联通块内,空格度数均大于等于二,黑白格子个数相等。

实际上这个题可以建立如下一个模型:
给定一个无向图,要你对图中所有的边进行黑白染色,使得最终每个点都只连接一条黑边,问是否存在且仅存在一种合法方案。

代码

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

#define MAXN 2100

using namespace std;

typedef pair<int,int> pr;

char map[MAXN][MAXN];
int outDegree[MAXN][MAXN];
int n,m;
char s[MAXN];
int xx[]={1,-1,0,0},yy[]={0,0,1,-1};
char tag[4][5]={"^v","v^","<>","><"};
queue<pr>q;
bool vis[MAXN][MAXN];

void NotUnique()
{
    cout<<"Not unique\n"<<endl;
    exit(0);
}

int tot=0;

bool inMap(int x,int y)
{
    if(x<1||x>n||y<1||y>m) return false;
    if(map[x][y]!='.') return false;
    return true;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",map[i]+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if(map[i][j]!='.') continue;
            for(int dir=0;dir<4;dir++)
            {
                int newi=i+xx[dir],newj=j+yy[dir];
                if(!inMap(newi,newj)) continue;
                outDegree[i][j]++;
            }
            if(outDegree[i][j]==0) NotUnique();
            if(outDegree[i][j]==1) q.push(make_pair(i,j));
        }
    while(!q.empty())
    {
        bool find=false;
        pr now=q.front();
        q.pop();
        if(map[now.first][now.second]!='.') continue;
        for(int dir=0;dir<4;dir++)
        {
            int newx=now.first+xx[dir],newy=now.second+yy[dir];
            if(!inMap(newx,newy)) continue;
            map[now.first][now.second]=tag[dir][0];
            map[newx][newy]=tag[dir][1];
            for(int dir1=0;dir1<4;dir1++)
            {
                int nextx=newx+xx[dir1],nexty=newy+yy[dir1];
                if(inMap(nextx,nexty))
                {
                    outDegree[nextx][nexty]--;
                    if(outDegree[nextx][nexty]==1)
                    {
                        q.push(make_pair(nextx,nexty));
                    }
                }
            }
            break; //!!!!!!
        }
    }
    bool flag=true;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(map[i][j]=='.')
            {
                flag=false;
                break;
            }
    if(!flag) NotUnique();
    for(int i=1;i<=n;i++)
        printf("%s\n",map[i]+1);
    return 0;
}

C. Drazil and Park(516C)

题目链接

http://codeforces.com/contest/516/problem/C

题目大意

在一个环形跑道上种着 n 个树,给出每个树的高度以及两个相邻的树之间的距离,定义从树a跑到树 b 的代价是2(ha+hb)+dista,b m 次询问,每次标记某段区间里的树不能使用,问从一个树跑到另一个树的代价最大值。

思路

首先把这个长度为n的环断开变成长度为 2n 的链,那么就是在这个链上询问某个区间内最大的 2(ha+hb)+dista,b 值,这个问题非常类似于最大子段和问题,最大子段和问题就是用线段树维护两个标记 Lmax,Rmax ,分别代表每个区间由左端点向右延伸的最大值、由右端点向左延伸的最大值。还要维护 sum,maxsum 标记,分别代表该区间长度以及该区间的最大子段和。

在这个问题里多了个 h 值,我们可以让Lmax代表每个区间由左端点向右延伸的最大值+左端点的 h 值,让Rmax代表每个区间由右端点向左延伸的最大值+右端点的 h 值,这样维护出的maxsum标记就能代表该区间最大子段和+最大子段两端的 h <script type="math/tex" id="MathJax-Element-964">h</script>值

代码

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

#define MAXN 210000
#define INF 0x3f3f3f3f

using namespace std;

typedef long long int LL;

struct Info
{
    LL Lmax,Rmax,sum,maxsum;
}info[MAXN<<2],ans;
int n,m;
LL d[MAXN],h[MAXN];

void pushup(Info &o,Info lc,Info rc,LL len)
{
    o.Lmax=max(lc.Lmax,lc.sum+len+rc.Lmax);
    o.Rmax=max(rc.Rmax,rc.sum+len+lc.Rmax);
    o.sum=lc.sum+len+rc.sum;
    o.maxsum=max(max(lc.maxsum,rc.maxsum),lc.Rmax+len+rc.Lmax);
}

void Build(int o,int L,int R)
{
    if(L==R)
    {
        info[o].Lmax=info[o].Rmax=2*h[L];
        return;
    }
    int M=(L+R)>>1;
    Build(o<<1,L,M);
    Build(o<<1|1,M+1,R);
    pushup(info[o],info[o<<1],info[o<<1|1],d[M]);
}

void query(int o,int L,int R,int ql,int qr)
{
    if(ql<=L&&R<=qr)
    {
        if(ql==L) ans=info[o];
        else pushup(ans,ans,info[o],d[L-1]);
        return;
    }
    int M=(L+R)>>1;
    if(ql<=M) query(o<<1,L,M,ql,qr);
    if(qr>M) query(o<<1|1,M+1,R,ql,qr);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%I64d",&d[i]);
        d[i+n]=d[i];
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%I64d",&h[i]);
        h[i+n]=h[i];
    }
    Build(1,1,n*2);
    for(int i=1;i<=m;i++)
    {
        int x,y,L,R;
        scanf("%d%d",&L,&R);
        if(L>R)
        {
            x=R+1;
            y=L-1;
        }
        else
        {
            x=R+1;
            y=n+L-1;
        }
        query(1,1,2*n,x,y);
        printf("%I64d\n",ans.maxsum);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值