2016 Multi-University Training Contest 5 解题报告

1.1003-HDU 5783 Divide the Sequence(贪心)

题意:给出一个字符串,要求尽可能多地切开这个字符串,使得每个子串的所有前缀和都不小于0.

题解 :贪心。如果都是整数当然是每个数都分成一个子串。如果有负数的话,每个负数都要与他两边的数组合起来才可以。又要保证所有的前缀和都不小于0,所以负数只能与前面的数组成一段,贪心,从后往前加直到不小于0组成一段,减去消耗掉的几个数。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAX=1000000+10;
int a[MAX];

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        int ans=n;
        for(int i=n-1;i>=0;i--)
        {
            if(a[i]<0)
            {
                long long  sum=a[i];
                int cnt=0;
                while(sum<0)
                {
                    sum=sum+a[--i];
                    cnt++;
                }
                ans-=cnt;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

2.1011-HDU 5791 Two(DP)

题意:问两个序列有多少个相同的子序列(可以不连续)?

题解:dp[i][j]表示A序列前i个数和B序列前j个数的相同子序列对有多少个。

状态转移:

1.A[i]==B[j],dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+dp[i-1][j-1]+1=dp[i-1][j]+dp[i][j-1]+1

2.A[i]!=B[j],dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAX=1000+10;
const int MOD=1000000007;
int dp[MAX][MAX];
int A[MAX],B[MAX];

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&A[i]);
        for(int i=1;i<=m;i++)
            scanf("%d",&B[i]);
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(A[i]==B[j])
                    dp[i][j]=(dp[i-1][j]%MOD+dp[i][j-1]%MOD+1)%MOD;
                else dp[i][j]=(dp[i-1][j]%MOD+dp[i][j-1]%MOD-dp[i-1][j-1]%MOD)%MOD;
            }
        }
        printf("%d\n",(dp[n][m]+MOD)%MOD);
    }
    return 0;
}

3.1012-HDU 5792 World is Exploding(树状数组)

题意:问在一个序列中有多少个四元组(a,b,c,d)(a,b,c,d为下标且两两都不相等,1≤a<b≤n,1≤c<d≤n)使得Aa<Ab,Ac>Ad?

题解:(a,b)为递增对,(c,d)为递减对,求出总共的递增对和递减对相乘,减去重复(因为四个下标两两不相等)的个数即可。

重复包括以下情况:

1.b=c


2.a=c


3.b=d


4.a=d


所以我们求每个点的递增对和递减对,即每个点左边比他小的数ls[ ],左边比他大的数lg[ ],右边比他大右边比他小的数rs[ ],右边比他小的数rg[ ],这样总的递增对为 ls[ ]的和,总的递减对为rs[ ]的和,减去重复的对即ls[i]*rs[i]+lg[i]*ls[i]+rs[i]*rg[i]+lg[i]*rg[i]。

统计个数用树状数组即可,和上次求冒泡排序交换次数的用法一样。

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <climits>
#include <cstring>
using namespace std;
const int MAX=50000+10;
typedef long long LL;
int ls[MAX],lg[MAX],rs[MAX],rg[MAX];
int a[MAX],tmp[MAX];
int c[MAX];
int N;
int sum(int x)
{
    int sum=0;
    while(x>0)
    {
        sum+=c[x];
        x-=(x&(-x));
    }
    return sum;
}
void add(int x,int value)
{
    while(x<=N)
    {
        c[x]+=value;
        x+=(x&(-x));
    }
}
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            tmp[i]=a[i];
        }
        sort(tmp,tmp+n);
        N=unique(tmp,tmp+n)-tmp;
        for(int i=0;i<n;i++)
        {
            a[i]=lower_bound(tmp,tmp+N,a[i])-tmp+1;
        }
        LL up=0,down=0;
        memset(c,0,sizeof(c));
        for(int i=0;i<n;i++)
        {
            ls[i]=sum(a[i]-1);
            lg[i]=sum(N)-sum(a[i]);
            add(a[i],1);
            up+=ls[i];
        }
        memset(c,0,sizeof(c));
        for(int i=n-1;i>=0;i--)
        {
            rs[i]=sum(a[i]-1);
            rg[i]=sum(N)-sum(a[i]);
            add(a[i],1);
            down+=rs[i];
        }
        LL ans=up*down;
        for(int i=0;i<n;i++)
        {
            ans-=ls[i]*rs[i]+lg[i]*ls[i]+rs[i]*rg[i]+lg[i]*rg[i];
        }
        cout<<ans<<endl;
    }
    return 0;
}

4.1001-HDU 5781 World is Exploding(概率DP)

题意:

Alice忘记自己在银行里存了多少钱,只记得是闭区间[0,k]内的一个整数每次取,Alice会取y RMB的钱,如果余额足够则被取出,否则会被警告一次。若警告次数超过w次,Alice就会被警察抓走,在不被警察抓走的前提下,Alice采取最优策略,问期望尝试取钱多少次能够取完Alice存的所有钱。

题意真是难懂。

题解:

设DP[i][j]表示当前存款不超过 i ,且可以被警告的 j 次的取钱次数的期望。

状态转移:

首先钱数在【0,i】之间等概率分布,那么取钱 y 可能有两种情况:

1. y>存款,取钱失败,被警告一次,转移到DP[i-1][j-1]

2. y<=存款,取钱成功,存款减少,转移到DP[i-y][j]

转方程为DP[i][j]=P(y<存款)*DP[i-1][j-1]+P(y>存款)*DP[i-y][j]

那么概率P(y<=存款)的情况当然是存款数在【y,i】时,所以P(y<=存款)=(i-y+1)/(i+1),概率P(y>存款)的情况当然是存款数在【0,y-1】时,P(y>存款)=y/(i+1)。

所以:


边界DP[0][i]表示没钱了,次数自然是0,DP[i][0]表示没有警告次数了,所以期望次数应该是INF,因为不能再不能抓走的情况下取出钱。

这样复杂度为:O(k*K*W),但是因为Alice采用最优策略(二分),所以次数最多次,不会超过11次,w=min(w,11)。

可以先预处理数DP[2000][12].

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int MAX=2000+10;
const int INF=0x3f3f3f3f;
double dp[MAX][20];

void init()
{
    for(int i=0;i<MAX;i++)
        dp[i][0]=INF;
    for(int i=0;i<12;i++)
        dp[0][i]=0;
    for(int i=1;i<MAX;i++)
    {
        for(int j=1;j<12;j++)
        {
            double mmin=INF;
            for(int k=1;k<=i;k++)
            {
                mmin=min(mmin,dp[i-k][j]*(i-k+1.0)/(i+1)+dp[k-1][j-1]*k/(i+1.0)+1);
            }
            dp[i][j]=mmin;
        }
    }
}
int main()
{
    init();
    int k,w;
    while(scanf("%d%d",&k,&w)==2)
    {
        printf("%.6lf\n",dp[k][min(w,11)]);
    }
    return 0;
}

5. 1004-HDU 5784 How Many Triangles

题意:给出一堆点,求能组成多少个不同的三角形?

题解:假设锐角三角形的个数为k1,钝角和直角三角形的个数为k2,设锐角的个数p1,钝角和直角的个数为p2,则p1=3k1+2*k2,p2=k2。所以,锐角三角形的个数为(p1-2*k2)/3=(p1-2*p2)/3。

所以,求出锐角的数量直角+钝角的个数,就可以求出锐角三角形的个数。

统计三种角。

以一个点为中心,对其余点做极角排序。接下来可在O(n)的时间内统计出每种角的个数。复杂度 O(n*n*logn)。

具体做法是极角排序完之后用三个指针来指向共线的点(0,90)范围内的点(0,180)范围内的点,依次遍历每个点就可以统计出所有的三种角的个数。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
typedef long long LL;
const int MAX=2000+10;
struct point
{
    LL x,y;
    point(LL _x = 0,LL _y = 0):x(_x),y(_y) {};
    point operator +(const point &a)
    {
        return point(this->x + a.x,this->y + a.y);
    }
    point operator -(const point &a)
    {
        return point(this->x - a.x,this->y - a.y);
    }
};
point p[MAX];
vector<point> v;
double det(const point &a,const point &b)
{
    return a.x * b.y - a.y * b.x;
}
double dot(const point &a,const point &b)
{
    return a.x * b.x + a.y * b.y;
}
bool polar_cmp(const point &a,const point &b)
{
    if (a.y * b.y <= 0)
    {
        if (a.y > 0 || b.y > 0) return a.y < b.y;
        if (a.y == 0 && b.y == 0) return a.x < b.x;
    }
    return det(a,b) > 0;
}
int main()
{
    int n;
    while(scanf("%d",&n) == 1)
    {
        for(int i=0; i<n; i++)
            scanf("%I64d%I64d",&p[i].x,&p[i].y);
        LL p1=0,p2=0;
        for(int i=0; i<n; i++)
        {
            v.clear();
            for(int j=0; j<n; j++)
            {
                if(i!=j) v.push_back(point(p[j] - p[i]));
            }
            sort(v.begin(),v.end(),polar_cmp);
            v.insert(v.end(),v.begin(),v.end());
            for(int j=0,k1=0,k2=0,k3=0; j<n-1; j++) //三个指针分别指向共线点,锐角和直角
            {
                while(k1<j+n-1&&det(v[j],v[k1])==0&&dot(v[j],v[k1])>0) k1++;//共线的点
                k2=max(k2,k1);
                while(k2<j+n-1&&det(v[j],v[k2])>0&&dot(v[j],v[k2])>0) k2++;//锐角
                k3=max(k3,k2);
                while(k3<j+n-1&&det(v[j],v[k3])>0) k3++;//直角&钝角
                p1+=k2-k1;
                p2+=k3-k2;
            }
        }
        printf("%I64d\n",(p1-2*p2)/3);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值