2018九江市赛

市赛终于没爆掉了

T1

这不傻逼题吗

我们知道,若两个分数\(\frac{w_i}{c_i}>\frac{w_j}{c_j}\),那么就会有\(w_i*c_j>w_j*c_i\)

所以直接用这个来判定大小(考场上想都没想就直接这么写了,因为精度问题直接做除法不行)

然后就是瞎搞了(比较还是排序随便写啊)

#include<iostream>
#include<string>
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;

struct node{
    long long w,c,id;
}lemon[100100];
int n,cnt=0,s;

long long read()
{
    long long x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

bool cmp(node p,node q)
{
    return p.w*q.c>p.c*q.w;
}

int main()
{
    freopen("market.in","r",stdin);
    freopen("market.out","w",stdout);
    scanf("%d%d",&n,&s);
    int i;
    for (i=1;i<=n;i++)
    {
        long long a,b;
        a=read();b=read();
        if (b<=s) 
            {lemon[++cnt].w=a;lemon[cnt].c=b;lemon[cnt].id=i;}
    }
    sort(lemon+1,lemon+1+cnt,cmp);
    printf("%I64d",lemon[1].id);
    return 0;
}
/*
4
4 15
4 10
8 10
10000 20
*/

T2

暴力是很显然的,无论是直接做还是拓扑排序

然后就在考场上推了1h怎么O(n)建图,我还是太菜了

数据范围告诉你肯定是\(O(n)或O(nlogn)\)

一个很明显的结论:如果\(d_i>d_j\),那么无论\(r_i与r_j\)的大小,武器\(i\)的分组一定不会在武器\(j\)的前面

那么我们可以按照\(d\)从大到小排序

然后我们记一个\(best_i\)表示第\(i\)组中最大的\(d\)\(r\),这显然是一个递减序排列的数组

很明显如果一个武器的\(d\)(或\(r\))大于\(best_i\)中的对应元素,那么这个武器的分组一定大于等于\(i\)

那么对于每一组新来的\(d与r\),我们在\(best\)中进行二分查找找出它的位置,分别记为\(pos_d与pos_r\)

然后当前武器的分组就可表示为\(min(pos_d,pos_r)\),同时更新当前分组的\(best\)

这题就做完了,下面程序二分写得很丑请见谅

#include<iostream>
#include<string>
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;

struct node{
    int d,r,id;
}wea[100100],best[100100];
int n,ans[100100],all=0;

int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

bool cmp(node p,node q)
{
    return p.d>q.d;
}

int find(int x)
{
    int pos1,pos2,l,r;
    if (all==0) return 1;
    if (best[1].d<wea[x].d) pos1=1;
    else if (best[all].d>wea[x].d) pos1=all+1;
    else
    {
        l=1,r=all;
        while (l<=r)
        {
            int mid=(l+r)>>1;
            if (best[mid].d<wea[x].d) {pos1=mid;r=mid-1;}
            else l=mid+1;
            //cout << "l=" << l << "  r=" << r << endl;
        }
    }
    if (best[1].r<wea[x].r) pos2=1;
    else if (best[all].r>wea[x].r) pos2=all+1;
    else 
    {
        l=1,r=all;
        while (l<=r)
        {
            int mid=(l+r)>>1;
            if (best[mid].r<wea[x].r) {pos2=mid;r=mid-1;}
            else l=mid+1;
            //cout << "l=" << l << "  r=" << r << "pos=" << pos2 <<endl;
        }
        //cout << endl;
    }
    //cout << pos1 << " " << pos2 << endl;
    return min(pos1,pos2);
}

void out()
{
    cout << all << endl;
    for (int i=1;i<=all;i++) cout << best[i].d << " " << best[i].r << endl;
    cout << endl;
}
    

int main()
{
    freopen("tank.in","r",stdin);
    freopen("tank.out","w",stdout);
    n=read();
    int i;
    for (i=1;i<=n;i++) {wea[i].d=read();wea[i].r=read();wea[i].id=i;}
    sort(wea+1,wea+1+n,cmp);
    memset(ans,0,sizeof(ans));
    memset(best,0,sizeof(best));
    //for (i=1;i<=n;i++) cout << wea[i].d << " " << wea[i].r << " " << wea[i].id << endl;
    for (i=1;i<=n;i++)
    {
        int pos=find(i);
        ans[wea[i].id]=pos;
        if (all<pos) all++;
        best[pos].d=max(best[pos].d,wea[i].d);
        best[pos].r=max(best[pos].r,wea[i].r);
        //out();
    }
    for (i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}
/*
5
1 4
2 2
3 3
4 1
5 5
*/

T3

考试的时候很简单的想了下这应该是一个树形dp,然后就果断弃疗了

由于题面很长,我们会引用题面的几部分来简化题意

周克华深知多走一分钟路就多一分钟暴露的危险,而且他之前已经完全摸清了辖区的地形,因此他总是走最短路,也就是,他访问任何一个结点时,走的路线都是从银行到这里的最短路。为了简化题目,我们保证从银行(结点1)到任何一个结点的最短路都是唯一的。

这一段是在告诉你:罪犯会在以1号节点为根节点的最短路树上运动

只要有相邻的结点能满足“不走回头路、只走最短路”的前提,他一定会移动。如果有多个相邻结点可供选择,他会随机等概率选择一个作为他的移动目标。如果没有结点满足这一要求,那么周克华就会选择遁入深山之中,而可以想象在距离案发现场十万八千里的山区里抓捕周克华的难度,所以一旦周克华遁入山中,也就意味着Lemon的抓捕行动失败了。

这一段话在告诉你:罪犯会一直从深度小的地方向深度大的地方走,走到叶子结点时结束

有了上面两句话,我们可以开始考虑正解

首先把最短路树建出来,然后就在这棵树上跑dp,在这里的dp用bottom-up更为方便

记录\(dp[i][j]\)表示在以\(i\)为根节点的子树(包括\(i\))上布置\(j\)名警察抓住罪犯的概率,那么最终答案存储在\(dp[1][s]\)

\(dp[i][j]\)可以由两部分转移而来,一部分是在\(i\)节点上抓住罪犯,另一部分是在\(i\)节点上无法抓住罪犯,但是在\(i\)的子树上抓住了罪犯

很明显前一部分是数据已经给出,关键是后一部分

我们记一个辅助数组\(f[i]\)表示在子树上布置\(i\)名警察能抓住罪犯的概率,由于子树的信息全部已知,我们可以直接对当前节点的所有儿子节点做树上背包,最后乘上走到这一个儿子节点的概率即可

#include<iostream>
#include<string>
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
struct node{
    int to,nxt,cost;
}sq1[40010],sq2[40010];
int n,s,m,all1=0,all2=0,dis[210],head1[210],head2[210],son[210],pre[210];
double p[210][210],dp[210][210],f[210];
bool vis[210];
queue<int> q;
int read()
{
    int x=0,f=1;char ch=getchar();
    while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
    while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
    return x*f;
}

void add1(int u,int v,int w)
{
    all1++;sq1[all1].to=v;sq1[all1].nxt=head1[u];sq1[all1].cost=w;head1[u]=all1;
}

void add2(int u,int v,int w)
{
    all2++;sq2[all2].to=v;sq2[all2].nxt=head2[u];sq2[all2].cost=w;head2[u]=all2;
}

void init()
{
    n=read();m=read();
    memset(head1,0,sizeof(head1));
    memset(head2,0,sizeof(head2));
    int i,j;
    for (i=1;i<=m;i++)
    {
        int u,v,w;
        u=read();v=read();w=read();
        add1(u,v,w);add1(v,u,w);
    }
    s=read();
    for (i=1;i<=n;i++)
        for (j=1;j<=s;j++) scanf("%lf",&p[i][j]);
}

void spfa()
{
    memset(dis,0x3f,sizeof(dis));dis[1]=0;
    memset(vis,0,sizeof(vis));q.push(1);
    vis[1]=1;
    while (!q.empty())
    {
        int u=q.front(),i;q.pop();vis[u]=0;
        for (i=head1[u];i;i=sq1[i].nxt)
        {
            int v=sq1[i].to;
            if (dis[u]+sq1[i].cost<dis[v])
            {
                dis[v]=dis[u]+sq1[i].cost;pre[v]=u;
                if (!vis[v]) {vis[v]=1;q.push(v);}
            }
        }
    }
}

void make_sq()
{
    spfa();
    int i,j;
    for (i=2;i<=n;i++) add2(pre[i],i,1);
    memset(son,0,sizeof(son));
    for (i=1;i<=n;i++)
        for (j=head2[i];j;j=sq2[j].nxt) son[i]++;
    //for (i=1;i<=n;i++) cout << son[i] << " ";cout << endl;
}           

/*f[i]记录在以当前节点为根节点时在它的子树时布置i个警察抓住罪犯 
  dp[i][j]记录在以i为根节点的子树(包括自己)抓住罪犯的概率
  dp[i][j]包括两部分,一部分是在根节点i抓住罪犯,另一部分是罪犯在根节点i时逃脱,但是在子树上被抓住*/

void dfs(int u)
{
    int i,j,k;
    for (i=1;i<=s;i++) dp[u][i]=p[u][i];
    if (!head2[u]) return;//叶子结点只可能在当前节点抓住罪犯
    for (i=head2[u];i;i=sq2[i].nxt) dfs(sq2[i].to);//bottom-up dp
    memset(f,0.0,sizeof(f));
    for (i=head2[u];i;i=sq2[i].nxt)
    {
        for (j=s;j>=0;j--)
        {
            for (k=0;k<=j;k++)
                f[j]=max(f[j],f[j-k]+dp[sq2[i].to][k]);//对所有儿子节点进行背包 
        }
    }
    for (i=0;i<=s;i++) f[i]=f[i]/son[u];//乘上每个点可能被走到的概率
    for (i=0;i<=s;i++)
    {
        for (j=0;i+j<=s;j++)
        {
            dp[u][i+j]=max(dp[u][i+j],p[u][i]+1.0*f[j]*(1-p[u][i]));
        }
    }
}

void work()
{
    dfs(1);
    printf("%.4lf",dp[1][s]);
}

int main()
{
    freopen("catch.in","r",stdin);
    freopen("catch.out","w",stdout);
    init();
    make_sq();
    work();
    return 0;
}
/*
4 4
1 2 1
1 3 2
2 4 3
3 4 1
2
0.01 0.1
0.5 0.8
0.5 0.8
0.7 0.9
*/  

猜你喜欢

转载自www.cnblogs.com/zhou2003/p/9824265.html