前言
……终于改完了,像之前小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;}elseif (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);//dpprintf("%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]);
}