前言
……终于改完了,像之前小L一样崩溃。今天C组和B组一起做题,所以……
正题
题目1:教主的花园(jzoj1792)
一平面直角坐标系,在x轴的位置建立一堵墙,墙上有n道门,给出门的位置,询问两个坐标的距离(曼哈顿)。
输入输出(建议无视)
Input
输入的第1行为一个正整数N,为屏障上入口的个数。
第2行有N个整数,a1, a2, …, aN,之间用空格隔开,为这N个入口的横坐标。
第3行为一个正整数M,表示了M个询问。
接下来M行,每行4个整数x1, y1, x2, y2,有y1与y2均不等于0,表示了一个询问从(x1, y1)到(x2, y2)的最短路。
Output
输出共包含m行,第i行对于第i个询问输出从(x1, y1)到(x2, y2)的最短路距离是多少。
Sample Input
2
2 -1
2
0 1 0 -1
1 1 2 2
Sample Output
4
2
解题思路
有三种情况
1. 坐标之间不隔墙
2. 坐标之间隔墙,并且最近的墙在两个坐标之间:
3. 坐标之间隔墙,并且最近的墙在两个坐标之间
然后前两种情况之接输出曼哈顿距离,第三种情况输出两个距离门的距离和。
用二分确定最近的门
代码
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[100001],m,x,y,zx,zy,l,r,mid,last;
bool ok;
int abs(int x)
{
if (x<0) return 0-x;
else return x;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+1+n);//排序
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&x,&y,&zx,&zy);//输如
if (y<0 && zy>0 || y>0 && zy<0)//如果中间隔墙
{
l=1;r=n;last=2147483647;//last记录离门最近距离
ok=true;
while (l<=r)
{
mid=(int)(l+r)/2;
if (zx>a[mid] && x<a[mid] || zx<a[mid] && x>a[mid])//如果门在中心
{
l=mid;
last=0;
break;//退出
}
if (min(abs(a[mid]-x),abs(a[mid]-zx))*2<last)//如果找到更近的距离
{
last=min(abs(a[mid]-x),abs(a[mid]-zx))*2;//记录
if (a[mid]<x) l=mid;
else r=mid;
}
else
if (a[mid]<x) l=mid+1;
else r=mid-1;
}
printf("%d\n",last+abs(x-zx)+abs(zy-y));//输出
}
else printf("%d\n",abs(zx-x)+abs(zy-y));//输出
}
}
题目2:教主泡嫦娥(jozj1793)
有一个环形山地,有n个落脚点,可以从任意落脚点任意状态出发。每个落脚点的代价为
当教主从第i个落脚点,走到第j个落脚点的时候(i和j相邻)
j的海拔高于i的海拔:如果教主处于上升状态,教主需要耗费两段高度差的绝对值的体力;否则耗费高度差平方的体力。
j的海拔低于i的海拔:如果教主处于下降状态,教主需要耗费两段高度差的绝对值的体力;否则耗费高度差平方的体力。
求走一圈的最小代价
输入输出(建议无视)
Input
输入的第一行为两个正整数N与M,即落脚点的个数与切换状态所消耗的体力。
接下来一行包含空格隔开的N个正整数,表示了每个落脚点的高度,题目保证了相邻落脚点高度不相同。
Output
输出仅包含一个正整数,即教主走一圈所需消耗的最小体力值。
注意:C++选手建议使用cout输出long long类型整数。
Sample Input
6 7
4 2 6 2 5 6
Sample Output
27
解题思路
如果枚举落脚点肯定会超时,所以我们就不能这么做。
用f[i][j][k]表示在第i个落脚点,状态为j,在当前有没有改变状态。
然后O(n)的时间复杂度轻易过
动态转移方程:
f[i][j][0]=f[i-1][j][0]+abs(a[i]-a[i-1]) 直接走过
f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+abs(a[i]-a[i-1]) 改变状态
f[i][j][0]=f[i-1][j][0]+pows(a[i]-a[i-1])
f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+pows(a[i]-a[i-1])
在这题被逼疯,默默AC不想说话
代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
long long f[10001][2][2],ans;
int a[10001],n,m;
long long abs(long long x)
{
if (x<0) return 0-x;
else return x;
}
long long pows(long long x)
{return x*x;}
void dp()
{
f[0][1][1]=f[0][0][1]=1e16-1;
for (int i=1;i<=n;i++)
for (int j=0;j<2;j++)
if ((a[i]<a[i-1])^j)
{
f[i][j][0]=f[i-1][j][0]+abs(a[i]-a[i-1]);
f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+abs(a[i]-a[i-1]);
}
else
{
f[i][j][0]=f[i-1][j][0]+pows(a[i]-a[i-1]);
f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+pows(a[i]-a[i-1]);
}//动态转移
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
a[n]=a[0];
memset(f,0ll,sizeof(f));
dp();//dp一次
ans=min(min(f[n][0][0],f[n][0][1]),min(f[n][1][0],f[n][1][1]));//取最小情况
memset(f,0ll,sizeof(f));
f[0][0][0]=1e16-1;//从f[0][1][0]推过去
dp();//dp一次
ans=min(ans,f[n][1][1]-m);//取值
memset(f,0ll,sizeof(f));
f[0][1][0]=1e16-1;//从f[0][0][0]推过去
dp();//dp一次
ans=min(ans,f[n][0][1]-m);//取值
cout<<ans<<endl;
}
题目3:保镖排队(jzoj1794)
有n个保镖,除第一个保镖外每个保镖都有一个上司,每个上司的下属都有在上司心目中的武力值。排队要求:
①互为上司-下属的两个保镖,上司在前,下属在后
②对于一个保镖的所有下属,武功实力较强的在前,较弱的在后。
求排队方案数
输入输出(建议无视)
Input
输入的第一行为一个正整数T,表示了数据组数。
对于每组数据:
第一行为一个正整数N。
接下来N行,每行描述一个保镖。
第i+1行,会有一个整数K,代表第i个保镖的下属个数,接下来K个数,代表第i个保镖的下属按照武功实力从高到低的编号。
Output
输出包括C行,每行对于每组数据输出方案数mod 10007后的结果。
Sample Input
2
5
2 2 3
2 4 5
0
0
0
7
2 2 3
2 4 5
2 6 7
0
0
0
0
Sample Output
3
10
解题思路
首先想到的是树形dp,由于保镖一没有上司所以一定是根。然后就是插空问题了,将3个保镖插入到3个空中,用组合的方法求插空方案数。用杨辉三角求组合。然后树形dp
代码
#include<cstdio>
#include<cstring>
using namespace std;
struct line{
int y,next;
}a[10001];//邻接表
int f[1001],s,ls[1001],n,k,t,w,head,num[1001];
int cost[1002][1002];
void dp(int x)
{
int q=ls[x];
f[x]=1;
if (q==0)
{
num[x]=1;
return;//没有子树
}
while (q!=0)
{
dp(a[q].y);
num[x]+=num[a[q].y];//累计
f[x]=((f[x]*f[a[q].y])%10007)*(cost[num[x]-1][num[a[q].y]-1])%10007;//求方案数
q=a[q].next;
}
num[x]++;//算上自己
}
int main()
{
cost[0][0]=1;
for (int i=1;i<=1001;i++)
for (int j=1;j<=i;j++)
{
cost[i][0]=1;
cost[i][j]=(cost[i-1][j]+cost[i-1][j-1])%10007;
}//杨辉三角
scanf("%d",&t);
for (int ti=1;ti<=t;ti++)
{
w=0;s=0;
memset(ls,0,sizeof(ls));
memset(f,0,sizeof(f));
memset(num,0,sizeof(num));//初始化
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&k);
for(int j=1;j<=k;j++)
{
scanf("%d",&a[++w].y);
a[w].next=ls[i];
ls[i]=w;
}
}
dp(1);//dp
printf("%d\n",f[1]);
}
}
题目4:教主的别墅(jzoj1795)
有N个人,有男有女,分成m段连续的部分,求分割的最小和最大字典序。
输入输出(建议无视)
Input
输入的第1行为两个正整数N与M,用空格分隔。
第2行包含一个长度为N的串,仅由字符组成,第i 个字符为0表示在这个位置上的LHXee为女生,若为1则为男生。
Output
输出文件包含两行,每行M个正整数,正整数之间用空格隔开,行末无多余空格。这M个正整数从左到右描述了你所分的每个组的人数。
第1行为字典序最小的方案,第2行为字典序最大的方案。
Sample Input
8 3
11001100
Sample Output
1 2 5
5 2 1
解题思路
这道题贪心竟然能过???
深搜TLE,贪心只能过样例
听某dalao讲用前缀和到第i个房间的男女差值(绝对值),然后每个房间最大差值为:
然后正推一次反推一次。
#include<cstdio>
#include<iostream>
using namespace std;
int n,m,sum[5000002],ans,anss,t,last,w,a[100002];
char c;
int abs(int x)
{
if (x<0) return 0-x;
else return x;
}//绝对值
int main()
{
scanf("%d%d\n",&n,&m);
for (int i=1;i<=n;i++)
{
cin>>c;
if (c=='0') sum[i]-=1;
else sum[i]+=1;
sum[i]+=sum[i-1];
}
ans=abs(sum[n])/m;//求最大差值
if (abs(sum[n])%m!=0) ans++;//向上取整
if (sum[n]==0)
{
int l=0;
for (int i=1;i<=n;i++)
if (sum[i]==0) l++;
if (l>=m) ans=0;
else ans=1;//能否做到没有差值
}
t=m;last=0;
for (int i=1;i<=n;i++)
{
anss=abs(sum[n]-sum[i])/(t-1);
if (abs(sum[n]-sum[i])%(t-1)!=0) anss++;
if (abs(sum[i]-sum[last])<=ans && anss<=ans)
{
printf("%d ",i-last);
last=i;
t--;
if (t==1) break;
}
}//查找
printf("%d\n",n-last);
ans=abs(sum[n])/m;
if (abs(sum[n])%m!=0) ans++;
if (sum[n]==0)
{
int l=0;
for (int i=1;i<=n;i++)
if (sum[i]==0) l++;
if (l>=m) ans=0;
else ans=1;
}
t=m;last=n;w=0;
for (int i=n-1;i>=1;i--)
{
anss=abs(sum[i])/(t-1);
if (abs(sum[i])%(t-1)!=0) anss++;
if (abs(sum[last]-sum[i])<=ans && anss<=ans)
{
a[++w]=last-i;
last=i;
t--;
if (t==1) break;
}
}//倒着查找
a[++w]=last;//记录
for (int i=w;i>=1;i--) printf("%d ",a[i]);
}