hdu5696~5701
主要利用数据随机的特性。之前没接触过这类题,主要利用随机数据分布的特点,将一些 O(n2) 的时间复杂度用调和级数转化为 O(nlogn) 的时间复杂度。除此之外,还有一些特性,例如长度为 n 的最长上升序列的长度大约为 logn , n 个随机点的凸包点集大小大约为 logn 等等。。之后会收集下此类的题。回到这个题上,先说做法,递归处理区间 [l,r] 找出区间中的最小值和最大值,更新后,再去递归区间 [l,k−1] 和区间 [k+1,r] 。其中 k 是区间 [l,r] 中最小值的下标。再利用 ans[i]=max(ans[i],ans[i+1]) 更新得到答案。显然这个做法是正确的,又由于数据是随机的,所以这个时间复杂度我们可以进行看成 O(nlogn) 的。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
using namespace std;
typedef long long LL;
LL ans[110000];
LL a[110000];
void work(int l,int r)
{
if (l>r) return ;
if (l==r)
{
ans[1]=max(ans[1],a[l]*a[l]);
return ;
}
int minn=l,maxx=l;
for (int i=l;i<=r;i++)
{
if (a[i]<a[minn]) minn=i;
if (a[i]>a[maxx]) maxx=i;
}
ans[r-l+1]=max(ans[r-l+1],a[minn]*a[maxx]);
work(l,minn-1);
work(minn+1,r);
}
int n;
int main()
{
while (scanf("%d",&n)==1)
{
memset(ans,0,sizeof(ans));
for (int i=1;i<=n;i++)
{
scanf("%I64d",&a[i]);
}
work(1,n);
for (int i=n-1;i>=1;i--)
ans[i]=max(ans[i],ans[i+1]);
for (int i=1;i<=n;i++)
printf("%I64d\n",ans[i]);
}
return 0;
}
这里主要用到求解最大/最小乘积生成树的思想,具体做法之后会专门写一篇算法学习,先放上神犇的链接:
http://www.cnblogs.com/autsky-jadek/p/3959446.html
然后题目中要求要第一关键字之和至少要到 m ,我们只需要用背包搞一下就行了。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
using namespace std;
typedef long long LL;
const LL INF=1e18;
int n,m;
LL pa[500],pb[500],pc[500];
LL a[500],b[500],c[500];
LL w[500];
LL A[1100],B[1100];
struct point
{
LL x,y;
}ans;
LL cross(point a,point b)
{
return a.x*b.y-a.y*b.x;
}
int d[500];
LL sum;
LL f[1100];
int cmp(int a,int b)
{
return pa[a]<pa[b];
}
point find(LL k1,LL k2)
{
point num={0,0};
for (int i=1;i<=n;i++)
w[i]=-b[i]*k2+c[i]*k1;
memset(A,0,sizeof(A));
memset(B,0,sizeof(B));
for (int i=0;i<=sum;i++)
f[i]=-INF;
f[0]=0;
int pre=0;
for (int i=1;i<=n;i++)
{
pre+=a[i];
for (int j=pre,k1=j-a[i];j>=a[i];j--,k1--)
if (f[k1]>-1e16&&f[k1]+w[i]>f[j])
{
f[j]=f[k1]+w[i];
A[j]=A[k1]+b[i];
B[j]=B[k1]+c[i];
}
}
LL now=-INF;
for (int i=m;i<=sum;i++)
if (f[i]>now)
{
now=f[i];
num=(point){A[i],B[i]};
}
if (num.x*num.y<ans.x*ans.y) ans=num;
return num;
}
void work(point k1,point k2)
{
if (k1.x==k2.x&&k1.y==k2.y)
{
k2.x++,k2.y++;
}
int ka=k1.x-k2.x,kb=k1.y-k2.y;
point now=find(ka,kb);
point aa={k1.x-k2.x,k1.y-k2.y};
point bb={now.x-k2.x,now.y-k2.y};
if (cross(aa,bb)>0)
{
work(k1,now);
work(now,k2);
}
}
int main()
{
while (scanf("%d%d",&n,&m)==2)
{
for (int i=1;i<=n;i++)
{
scanf("%I64d %I64d %I64d",&pa[i],&pb[i],&pc[i]);
d[i]=i;
}
sort(d+1,d+1+n,cmp);
for (int i=1;i<=n;i++)
a[i]=pa[d[i]],b[i]=pb[d[i]],c[i]=pc[d[i]];
ans.x=ans.y=2100000;
sum=0;
for (int i=1;i<=n;i++)
sum+=a[i];
work(find(1,0),find(0,-1));
printf("%I64d\n",ans.x*ans.y);
}
return 0;
}
组合数学题。我们能够发现,当n固定时,整个答案为n-2阶等差数列,之后能推出公式
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
LL f[1100000],inv[1100000],ff[1100000];
LL C(int n,int m)
{
if (n==0||m==0) return 1;
LL res=(f[n]*inv[m])%mod;
res=(res*inv[n-m])%mod;
return res;
}
int n,m;
int main()
{
f[0]=1;
for (int i=1;i<=1000010;i++)
f[i]=(f[i-1]*i)%mod;
ff[1]=ff[0]=inv[1]=inv[0]=1;
for (int i=2;i<=1000010;i++)
{
inv[i]=(LL)(mod-mod/i)*inv[mod%i]%mod;
ff[i]=inv[i];
}
for (int i=2;i<=1000010;i++)
inv[i]=(inv[i-1]*inv[i])%mod;
while (scanf("%d %d",&n,&m)==2)
{
printf("%I64d\n",C(n+m-4,n-2));
}
return 0;
}
首先我们能想到二分答案,然后问题在于如何判断一个答案是否存在方案。假设我们需要判断时间最多为 x 的方案是否存在。对于一个运输 l[i] 和 r[i] ,如果之间的距离小于 x ,我们就不用考虑了。如果大于 x ,我们要找出新建的站点两端 l 和 r 所在的最大的区间即可,这里我们只用讨论四种方案就行。最后看区间是否为空就可以判断是否存在方案了。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
using namespace std;
typedef long long LL;
int n,m;
int l[1100000],r[1100000];
int lx,rx,ly,ry;
void change(int x,int y)
{
int l=x-y,r=x+y;
lx=min(lx,l),ly=min(ly,r);
rx=max(rx,l),ry=max(ry,r);
}
int check(int x)
{
int x1=-1e9,x2=1e9,y1=-1e9,y2=1e9;
for (int i=1;i<=m;i++)
{
if (r[i]-l[i]>x)
{
lx=ly=1e9,rx=ry=-1e9;
change(l[i],r[i]-x);
change(l[i],r[i]+x);
change(l[i]-x,r[i]);
change(l[i]+x,r[i]);
x1=max(x1,lx),y1=max(y1,ly);
x2=min(x2,rx),y2=min(y2,ry);
if (x1>x2||y1>y2) return 0;
}
}
return 1;
}
int solve()
{
int l=0,r=n-1;
while (l<r)
{
int mid=(l+r)>>1;
if (check(mid)) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
while (scanf("%d %d",&n,&m)==2)
{
for (int i=1;i<=m;i++)
{
scanf("%d %d",&l[i],&r[i]);
if (l[i]>r[i]) swap(l[i],r[i]);
}
int ans=solve();
printf("%d\n",ans);
}
return 0;
}
将线段的所有端点排序,对于同一位置的点,我们先考虑线段左端点的插入,再更新答案,最后考虑线段右端点的删除。那么如何更新答案呢。我们考虑以 i 为区间交的左端点的情况,由于所有数值为正,所以此时区间的右端点越大越好。如果此时插入的线段个数不少于 k ,那么后面一定有至少 k 个右端点,我们只要求出所有右端点中的第 k 大即可,那么这个是以 i 为左端点,线段右端点中第 k 大的位置为右端点的区间,更新下 ans 即可。插入和删除可以用线段树维护,不过我选择了模板功能强大的splay。插入时,将对应线段的右端点放进splay中,删除时,将对应线段右端点从splay中删除即可。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
using namespace std;
typedef long long LL;
int l[110000],r[110000];
vector<int> p;
struct point
{
int id,x,type;
}a[310000];
LL v[110000];
int num;
int cmp(point a,point b)
{
if (a.x!=b.x) return a.x<b.x;
return a.type<b.type;
}
const int maxn=110000;
struct node
{
int num,size,c[2],fa;
LL data;
}tree[maxn];
int root=0,sum=0;
void update(int x)
{
tree[x].size=tree[tree[x].c[0]].size+tree[tree[x].c[1]].size+tree[x].num;
}
void rotate(int x,int p)
{
int y=tree[x].fa;
if (tree[x].c[p]) tree[tree[x].c[p]].fa=y;
tree[y].c[p^1]=tree[x].c[p];
tree[x].c[p]=y;
tree[x].fa=tree[y].fa;
tree[y].fa=x;
if (tree[x].fa) if (y==tree[tree[x].fa].c[0]) tree[tree[x].fa].c[0]=x;
else tree[tree[x].fa].c[1]=x;
update(y);
update(x);
}
void splay(int x)
{
while (tree[x].fa!=0)
{
int y=tree[x].fa;
int z=tree[y].fa;
if (z==0)
{
if (tree[y].c[0]==x) rotate(x,1);
else rotate(x,0);
break;
}
int a=y==tree[z].c[0]?1:0;
int b=x==tree[y].c[0]?1:0;
if (a^b) rotate(x,b),rotate(x,a);
else rotate(y,a),rotate(x,b);
}
root=x;
}
void maintain(int x)
{
while (x!=0)
update(x),x=tree[x].fa;
}
void init(LL x,int sum)
{
tree[sum].data=x;
tree[sum].fa=0;
tree[sum].c[0]=0;
tree[sum].c[1]=0;
tree[sum].num=1;
tree[sum].size=1;
}
void insert(LL x,int sum)
{
int y=root;
int pos=0;
init(x,sum);
if (y==0)
{
root=sum;
return;
}
while (true)
{
tree[y].size++;
if (x==tree[y].data)
{
pos=y;
tree[y].num++;
maintain(y);
break;
}
int p=1;
if (x<tree[y].data) p=0;
if (tree[y].c[p]==0)
{
tree[y].c[p]=sum;
tree[sum].fa=y;
break;
}
else y=tree[y].c[p];
}
if (pos==0) pos=sum;
splay(pos);
}
int findpos(LL x,int z)
{
int p,y=root,d=0;
while (x!=tree[y].data)
{
p=0;
if (x>tree[y].data) p=1;
if (p&&z) d+=tree[tree[y].c[0]].size+tree[y].num;
y=tree[y].c[p];
}
if (z) return d+tree[tree[y].c[0]].size+1;
else return y;
}
int find(int x,int p)
{
x=findpos(x,0);
splay(x);
int y=root;
y=tree[y].c[p];
while (tree[y].c[p^1])
y=tree[y].c[p^1];
return y;
}
void del(LL x)
{
int pos=findpos(x,0);
splay(pos);
if (tree[pos].num>1)
{
tree[pos].num--;
tree[pos].size--;
}
else
{
if (tree[pos].c[0]!=0)
{
tree[tree[pos].c[0]].fa=0;
int f=find(tree[pos].data,0);
tree[tree[pos].c[1]].fa=f;
tree[f].c[1]=tree[pos].c[1];
maintain(tree[pos].c[1]);
root=tree[pos].c[0];
tree[pos].c[0]=0;
tree[pos].c[1]=0;
}
else
{
tree[tree[pos].c[1]].fa=0;
root=tree[pos].c[1];
}
}
}
LL finddata(int x)
{
int y=root;
while (x<=tree[tree[y].c[0]].size||x>tree[tree[y].c[0]].size+tree[y].num)
{
if (x<=tree[tree[y].c[0]].size) y=tree[y].c[0];
else
{
x-=tree[tree[y].c[0]].size+tree[y].num;
y=tree[y].c[1];
}
}
return tree[y].data;
}
LL ssum[110000];
int n,k,m;
int main()
{
while (scanf("%d %d %d",&n,&k,&m)==3)
{
root=sum=num=0;
for (int i=1;i<=n;i++)
scanf("%I64d",&v[i]);
for (int i=1;i<=n;i++)
ssum[i]=ssum[i-1]+v[i];
for (int i=1;i<=m;i++)
{
scanf("%d %d",&l[i],&r[i]);
num++;
a[num].id=i;a[num].x=l[i];a[num].type=1;
num++;
a[num].id=i;a[num].x=r[i];a[num].type=3;
}
for (int i=1;i<=n;i++)
{
num++;
a[num].id=i;
a[num].x=i;
a[num].type=2;
}
sort(a+1,a+1+num,cmp);
int now=0;
LL ans=0;
for (int i=1;i<=num;i++)
{
if (a[i].type==1)
{
now++;
insert(ssum[r[a[i].id]],++sum);
}
if (a[i].type==2)
{
if (now<k) continue;
ans=max(ans,finddata(now+1-k)-ssum[a[i].id-1]);
}
if (a[i].type==3)
{
now--;
del(ssum[r[a[i].id]]);
}
}
printf("%I64d\n",ans);
}
return 0;
}
首先我们能判断出,我们不需要特殊考虑两个数的平均值为 x 的这种情况。之后我们就可以将其转化为经典的问题了。枚举中位数,然后以之为起点,向两边扫,遇到大于 x 的数置为1,遇到小于 x 的数置为-1,遇到等于 x 的数置为0即可。然后判断有多少区间和为0即可。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<bitset>
using namespace std;
typedef long long LL;
int n;
int a[9000],b[9100];
int d[18100];
const int bb=9040;
int main()
{
while (scanf("%d",&n)==1)
{
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
{
int ans=0;
memset(d,0,sizeof(d));
for (int j=1;j<=n;j++)
{
if (a[j]==a[i]) b[j]=0;
else if (a[j]>a[i]) b[j]=1;
else b[j]=-1;
}
int now=0;
for (int j=i;j>=1;j--)
{
now+=b[j];
d[now+bb]++;
}
now=0;
for (int j=i;j<=n;j++)
{
now+=b[j];
ans+=d[-now+bb];
}
printf("%d",ans);
if (i!=n) printf(" ");
else printf("\n");
}
}
return 0;
}