Codeforces #284 Div 1 简要题解

A. Crazy Town

题目链接

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

题目大意

给你一个无限大的区域,这个区域被 n 条形如Ax+By+C=0无限长的直线道路分割成若干个街区,给出A地和B地坐标,问从A地到B地最少要穿过多少条道路,注意不能穿过直线与直线的交点

思路

很容易想到,最少的穿越次数,就是与线段 AB 不含端点的中间部分相交的直线个数,证明大家自己意识流一下就好了,其实就是看有多少直线把AB两地完全分割开,这些直线是必须穿过的,而其他直线就不必穿过去了。

但是问题是怎么找这些直线。我刚开始非常naive地去求每条直线和直线 AB 的交点,然后判断交点是否在线段 AB 上,各种特判不说,推导过程也很容易打错。但是我看完一个红名爷的代码后才发现自己too simple了。注意到输入的直线都是 Ax+By+C=0 形式的,我们可以当成一个二元函数 f(x,y) ,显然只有 f(xA,yA) f(xB,yB) 符号相反,才能让这条直线穿过线段 AB 不含端点的中间部分

证明也很容易。假设这条直线和线段 AB 不含端点的中间部分的交点坐标为 (xt,yt) ,显然 f(xt,yt)=0 ,而从 (xt,yt) 开始,往一头走的话, f(x,y) 的值会变为负数,而往相反方向的另一头走, f(x,y) 的值会变为正数

红名爷就是巨啊!

代码

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

#define MAXN 10000
#define EPS 1e-7

using namespace std;

typedef long long int LL;

LL a[MAXN],b[MAXN],c[MAXN];
int n;

int dcmp(LL x)
{
    if(x>0) return 1;
    if(x<0) return -1;
    return 0;
}

int main()
{
    LL x1,y1,x2,y2;
    scanf("%I64d%I64d%I64d%I64d",&x1,&y1,&x2,&y2);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%I64d%I64d%I64d",&a[i],&b[i],&c[i]);
    int tot=0;
    for(int i=1;i<=n;i++)
        if(dcmp(a[i]*x1+b[i]*y1+c[i])!=dcmp(a[i]*x2+b[i]*y2+c[i]))
            tot++;
    printf("%d\n",tot);
    return 0;
}

B. Name That Tune

题目链接

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

题目大意

在《开门大吉》节目里(大雾),主持人给了你 n 首歌,每首歌对应一扇门,选手面对1n号大门,依次按响门上的门铃,门铃会播放一段音乐(将一首经典流行歌曲以单音色旋律的方式演绎),对于第 i 扇门,选手有Ti秒的时间听音乐,在每一秒时,选手有 Pi% 的概率能听出这首歌的名字,另外 100Pi% 的概率需要等到下一秒继续听,但是在 Ti 秒时选手一定能听出这首歌的名字,求前 T 秒选手可以听出的歌曲个数的期望。

思路

可以想到用DP解决此题。用f[i][j]表示当前到了第 j 秒,正在听第i首歌并听出这首歌的名字的概率。显然 f[0][0]=1 ,并可以推出下面的DP方程:

f[i][j]=f[i1][j1](1Pi)0Pi+f[i1][j2](1Pi)1Pi+...+f[i1][jTi](1Pi)Ti1Pi+f[i1][jTi](1Pi)Ti


f[i][j]=kTi1f[i1][jk](1Pi)k1Pi(k1k)+f[i1][jTi](1Pi)Ti(TiTi)

我们可以优化这个DP,由于 f[i][j] f[i][j1] 的DP方程高度相似,因此我们可以维护 f[i][j1] 里的一个值 tmp

tmp=f[i1][j2](1Pi)0+f[i1][j3](1Pi)1+...+f[i1][jTi1](1Pi)Ti1

那么

f[i][j]=Pi(1Pi)tmp+f[i1][j1]Pi+f[i1][jTi](1Pi)Ti

这只是特殊情况,由于 tmp 里最多只能有 Ti 项,因此若 jTi1>=0 ,则在DP到 f[i][j] 时,需要在 tmp 里减去 f[i1][jTi1](1Pi)Ti1

代码

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

#define MAXN 11000

using namespace std;

double f[2][MAXN],ans=0;
int n,t;
int now=1,pre=0;

int main()
{
    scanf("%d%d",&n,&t);
    f[pre][0]=1;
    for(int i=1;i<=n;i++)
    {
        memset(f[now],0,sizeof(f[now]));
        double Pi;
        int Ti;
        scanf("%lf%d",&Pi,&Ti);
        Pi/=100;
        double tmp=0;
        for(int j=i;j<=t;j++)
        {
            tmp*=(1-Pi);
            tmp+=f[pre][j-1];
            if(j-Ti-1>=0) tmp-=f[pre][j-Ti-1]*pow(1-Pi,Ti);
            f[now][j]+=tmp*Pi;
            if(j-Ti>=0) f[now][j]+=f[pre][j-Ti]*pow(1-Pi,Ti);
            ans+=f[now][j];
        }
        swap(now,pre);
    }
    printf("%.6f\n",ans);
    return 0;
}

C. Array and Operations

题目链接

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

题目大意

给你 n 个数a1...an,以及 m 个操作数对(i,j),每次操作时你可以选择一个 (i,j) ,统一除以 ai,aj 的一个公约数,问你最多能进行多少次这样的操作

思路

显然,为了让步数最大化,我们每次除以的公约数都是质数。而且对于不同的除数操作,它们之间互不干扰,相同的除数操作会互相干扰。

我们可以先预处理筛素数,然后枚举 [1,109] 内的质数 p ,用最大流求除以p的操作最多能进行多少次。由于题面限制 i+j 为奇数,则显然 i j中有一个是偶数,另一个是奇数,这样就能构建出一个类似二分图的网络流模型:源点向所有下标为奇数的点连容量为这个点里 p 的出现次数,所有下标为奇数的点向汇点连容量为这个点里p的出现次数,对于每个操作 (i,j) ( i 为奇数)i j 连容量为这两个点里p的出现次数的较小值,代表对 (i,j) 做除 p 操作的最大次数。最大流就是除以p的操作最多能进行的次数。

然后每个 ai 除完了以后可能还有剩下的一个大于 109 的大质数。我们再一次建图:源点向所有下标为奇数的点连容量为1或0(该点还剩余的值大于1就是1,否则为0),所有下标为奇数的点向汇点连容量为1或0(该点还剩余的值大于1就是1,否则为0),对于每个操作 (i,j) ( i 为奇数),若i,j剩余的值均大于1, i 就向j连容量为1,再跑一次最大流加入答案即可。

代码

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

#define MAXV 1000
#define MAXE 21000
#define INF 0x3f3f3f3f

using namespace std;

typedef pair<int,int> pr;

int S,T;
int n,m;
int a[MAXV];
pr opt[MAXV];

struct edge
{
    int u,v,cap,next;
}edges[MAXE*2];

int head[MAXV],nCount=0;

void AddEdge(int U,int V,int C)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].cap=C;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

void add(int U,int V,int C)
{
    AddEdge(U,V,C);
    AddEdge(V,U,0);
}

int q[MAXE*2];
int layer[MAXV];
bool inX[MAXV],inY[MAXV];

bool CountLayer()
{
    memset(layer,-1,sizeof(layer));
    int h=0,t=1;
    q[h]=S;
    layer[S]=1;
    while(h<t)
    {
        int u=q[h++];
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(layer[v]==-1&&edges[p].cap)
            {
                layer[v]=layer[u]+1;
                q[t++]=v;
            }
        }
    }
    return layer[T]!=-1;
}

int DFS(int u,int flow)
{
    int used=0;
    if(u==T) return flow;
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(layer[v]==layer[u]+1&&edges[p].cap)
        {
            int tmp=DFS(v,min(flow-used,edges[p].cap));
            edges[p].cap-=tmp;
            edges[p^1].cap+=tmp;
            used+=tmp;
            if(used==flow) break;
        }
    }
    if(!used) layer[u]=-1;
    return used;
}

int Dinic()
{
    int maxflow=0;
    while(CountLayer())
        maxflow+=DFS(S,INF);
    return maxflow;
}

bool isPrime[31623];
int primes[100000],tot=0;

void getPrime()
{
    memset(isPrime,true,sizeof(isPrime));
    isPrime[0]=isPrime[1]=false;
    for(int i=2;i<31623;i++)
    {
        if(!isPrime[i]) continue;
        primes[++tot]=i;
        for(int j=i+i;j<31623;j+=i)
            isPrime[j]=false;
    }
}

int cnt[MAXV]; //cnt[i]=a[i]可以除prime的次数

int solve(int prime)
{
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
        while(a[i]%prime==0)
        {
            a[i]/=prime;
            cnt[i]++;
        }
    nCount=1;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)
    {
        if(inX[i])
            add(S,i,cnt[i]);
        else
            add(i,T,cnt[i]);
    }
    for(int i=1;i<=m;i++)
        add(opt[i].first,opt[i].second,min(cnt[opt[i].first],cnt[opt[i].second]));
    return Dinic();
}

int main()
{
    int ans=0;
    getPrime();
    memset(head,-1,sizeof(head));
    S=MAXV-2,T=MAXV-1;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(i&1) inX[i]=true;
        else inY[i]=true;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&opt[i].first,&opt[i].second);
        if(inX[opt[i].second])
            swap(opt[i].first,opt[i].second);
    }
    for(int i=1;i<=tot;i++)
        ans+=solve(primes[i]);
    memset(head,-1,sizeof(head));
    nCount=1;
    for(int i=1;i<=n;i++)
    {
        if(inX[i])
            add(S,i,1);
        else
            add(i,T,1);
    }
    for(int i=1;i<=m;i++)
        if(a[opt[i].first]==a[opt[i].second]&&a[opt[i].first]>1)
            add(opt[i].first,opt[i].second,1);
    ans+=Dinic();
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值