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;
}