T1 过路费
Floyd
题目:
跟所有人一样,农夫约翰以着宁教我负天下牛,休叫天下牛负我的伟大精神,日日夜夜苦思生财之道。为了发财,他设置了一系列的规章制度,使得任何一只奶牛在农场中的道路行走,都要向农夫约翰上交过路费。农场中由N(1 <= N <= 250)片草地(标号为1到N),并且有M(1 <= M <= 10000)条双向道路连接草地A_j和B_j(1 <= A_j <= N; 1 <= B_j<= N)。奶牛们从任意一片草地出发可以抵达任意一片的草地。FJ已经在连接A_j和B_j的双向道路上设置一个过路费L_j (1 <= L_j<= 100,000)。 可能有多条道路连接相同的两片草地,但是不存在一条道路连接一片草地和这片草地本身。最值得庆幸的是,奶牛从任意一篇草地出发,经过一系列的路径,总是可以抵达其它的任意一片草地。除了贪得无厌,叫兽都不知道该说什么好。FJ竟然在每片草地上面也设置了一个过路费C_i (1 <= C_i <= 100000)。从一片草地到另外一片草地的费用,是经过的所有道路的过路费之和,加上经过的所有的草地(包括起点和终点)的过路费的最大值。任劳任怨的牛们希望去调查一下她们应该选择那一条路径。她们要你写一个程序,接受K(1 <= K<= 10,000)个问题并且输出每个询问对应的最小花费。第i个问题包含两个数字s_i 和t_i(1 <= s_i <= N; 1 <= t_i <= N; s_i != t_i),表示起点和终点的草地。
【输入格式】
第1行: 三个空格隔开的整数: N, M和K
第2到第N+1行: 第i+1行包含一个单独的整数: C_i
第N+2到第N+M+1行:
第j+N+1行包含3个由空格隔开的整数: A_j, B_j和L_j
第N+M+2倒第N+M+K+1行:
第i+N+M+1行表示第i个问题,包含两个由空格隔开的整数s_i 和t_i
【输出格式】
第1到第K行: 第i行包含一个单独的整数,表示从s_i到t_i的最小花费。
【输入样例】
5 7 2
2
5
3
3
4
1 2 3
1 3 2
2 5 3
5 3 1
5 4 1
2 4 3
3 4 4
1 4
2 3
【输出样例】
8
9
【数据范围及约定】
见题目描述。
这个题也是无语了,考试的时候直接SPFA找到边权最小之后记录路径找到点权最大相加还得了30分QAQ……其实是不对的
正解Floyd
根据Floyd的原理,当最外层循环达到k时,已求路径的中间节点的编号都<k。
所以我们对结点的C值排一个序,按照C值的升序枚举k。
当枚举到k时,对于已求路径i→k和k→j,除i和j外中间经过的节点的权值一定小于等于k的权值,利用这一性质可以转移dis[i,j]=min{dis[i,j],data[i,k]+data[k,j]+max{c[k],max{c[i],c[j]}}}。Dis[i,j]表示从i到j的最小费用,data[i,j]表示从i到j不考虑点权的最小费用。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=255;
const int MAXM=10010;
const int INF=1000000000;
struct check{
int st,en;
}a[MAXM];
struct point{
int val,id;
bool friend operator <(point a,point b){
return a.val<b.val;
}
}c[MAXN];
int N,M,K;
int cc[MAXN];
int dis[MAXN][MAXN];
int dist[MAXN][MAXN];
int Min(int a,int b) {
return a<b?a:b;
}
int Max(int a,int b) {
return a>b?a:b;
}
void floyd() {
int i,j,k;
for(k=1;k<=N;++k) {
for(i=1;i<=N;++i){
for(j=1;j<=N;++j){
if(dis[i][j]>dis[i][c[k].id]+dis[c[k].id][j])
dis[i][j]=dis[i][c[k].id]+dis[c[k].id][j];
if(dist[i][j]>dis[i][c[k].id]+dis[c[k].id][j]+Max(c[k].val,Max(cc[i],cc[j])))
dist[i][j]=dis[i][c[k].id]+dis[c[k].id][j]+Max(c[k].val,Max(cc[i],cc[j]));
}
}
}
}
void init(){
int i,j,u,v,w;
for(i=0;i<MAXN;++i)
for(j=0;j<MAXN;++j){
dist[i][j]=INF;
if(i!=j)
dis[i][j]=INF;
}
scanf("%d%d%d",&N,&M,&K);
for(i=1;i<=N;++i) {
scanf("%d",&c[i].val);
cc[i]=c[i].val;
c[i].id=i;
}
for(i=1;i<=M;++i) {
scanf("%d%d%d",&u,&v,&w);
if(dis[u][v]<w) {
continue;
}
dis[u][v]=dis[v][u]=w;
}
for(i=1;i<=K;++i) {
scanf("%d%d",&a[i].st,&a[i].en);
}
sort(c+1,c+N+1);
floyd();
for(i=1;i<=K;++i) {
printf("%d\n",dist[a[i].st][a[i].en]);
}
}
int main(){
freopen("toll.in","r",stdin);
freopen("toll.out","w",stdout);
init();
}
T2 观光公交
贪心+模拟
没想到,一直在想DP,之后……
题目如下:
风景迷人的小城Y 市,拥有n 个美丽的景点。由于慕名而来的游客越来越多,Y 市特意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第0 分钟出现在1号景点,随后依次前往2、3、4……n 号景点。从第i 号景点开到第i+1 号景点需要Di 分钟。
任意时刻,公交车只能往前开,或在景点处等待。
设共有m 个游客,每位游客需要乘车1 次从一个景点到达另一个景点,第i 位游客在Ti 分钟来到景点Ai,希望乘车前往景点Bi(Ai<bi)。为了使所有乘客都能顺利到达目的地,公交车在每站都必须等待需要从该景点出发的所有乘客都上车后才能出发开往下一景点。
假设乘客上下车不需要时间。
一个乘客的旅行时间,等于他到达目的地的时刻减去他来到出发地的时刻。因为只有一辆观光车,有时候还要停下来等其他乘客,乘客们纷纷抱怨旅行时间太长了。于是聪明的司机ZZ 给公交车安装了k 个氮气加速器,每使用一个加速器,可以使其中一个Di 减1。对于同一个Di 可以重复使用加速器,但是必须保证使用后Di 大于等于0。
那么ZZ 该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?
【输入】
输入文件名为bus.in。
第1 行是3 个整数n, m, k,每两个整数之间用一个空格隔开。分别表示景点数、乘客数和氮气加速器个数。
第2 行是n-1 个整数,每两个整数之间用一个空格隔开,第i 个数表示从第i 个景点开往第i+1 个景点所需要的时间,即Di。
第3 行至m+2 行每行3 个整数Ti, Ai, Bi,每两个整数之间用一个空格隔开。第i+2 行表示第i 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。
【输出】
输出文件名为bus.out。共一行,包含一个整数,表示最小的总旅行时间。
【输入输出样例】
bus.in
3 3 2
1 4
0 1 3
1 1 2
5 2 3
bus.out
10
【输入输出样例说明】
对D2 使用2 个加速器,从2 号景点到3 号景点时间变为2 分钟。
公交车在第1 分钟从1 号景点出发,第2 分钟到达2 号景点,第5 分钟从2 号景点出发,第7 分钟到达3 号景点。
第1 个旅客旅行时间 7-0 = 7 分钟。
第2 个旅客旅行时间 2-1 = 1 分钟。
第3 个旅客旅行时间 7-5 = 2 分钟。
总时间 7+1+2 = 10分钟。
【数据范围】
对于10%的数据,k=0;
对于20%的数据,0<=k<=1;
对于40%的数据,2 ≤ n ≤ 50,1 ≤ m≤ 1,000,0 ≤ k ≤ 20,0 ≤ Di ≤ 10,0 ≤ Ti ≤ 500;
对于60%的数据,1 ≤ n ≤ 100,1 ≤ m≤ 1,000,0 ≤ k ≤ 100,0 ≤ Di ≤ 100,0 ≤ Ti ≤ 10,000;
对于100%的数据,1 ≤ n ≤ 1,000,1 ≤ m ≤ 10,000,0 ≤ k ≤ 100,000,0 ≤ Di ≤ 100,0 ≤ Ti ≤ 100,000。
设t[i]表示来到第i个景点的乘客最晚的时间,time[i]表示车到达第i个景点的最小时间。
因为每个乘客到达的时间已经固定,所以要使总时间最小,就是使Σtime[b[i]]最小,其中b[i]代表每位乘客的目的地。
先考虑不用加速器的情况。可以直接递推求出答案,time[i]= max(time[i - 1],t[i - 1]) + d[i - 1];
接下来再来考虑使用加速器减少的时间。对于每个加速器,我们必须使这个加速器获得最大的效益,即使尽可能多的乘客旅行时间减一。如果我们在i到i + 1间使用加速器的话,那么到i+ 1站的乘客的旅行时间都会减一,但是如果time[i+ 1]小于t[i+ 1]的话,车就要在i+ 1站等到t[i+ 1]所有的乘客上车,在i+ 1站以后下车的乘客的时间是一样的,也就是说这个加速器对后面下车的乘客没有影响。我们可以用递推求出每个i最远能影响的车站。
g[i] =g[i + 1] (time[i + 1] > t[i + 1])
g[i] = i+ 1 (time[i + 1] <= t[i + 1])
那么,我们用一个加速器所能减少一个单位时间的乘客就是sum[g[i]]- sum[i],每次找出使这个最大的i即可。然后把ans减掉,维护time,g
记得每次都要维护time和g,要不然不知道WA到什么时候,还有就是一定要看清楚数组大小以及每次循环的边界!
//#define READ
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXM=100010;
int n,m,k,ans;
int sum[MAXM],time[MAXM],f[MAXM];
int dist[MAXM],g[MAXM];
int Max(int a,int b) {
return a>b?a:b;
}
struct pas{
int arrive,st,en;
}a[MAXM];
void init(){
int i,j;
scanf("%d%d%d",&n,&m,&k);
for(i=1;i<n;++i) scanf("%d",&dist[i]);
for(i=1;i<=m;++i) {
scanf("%d%d%d",&a[i].arrive,&a[i].st,&a[i].en);
sum[a[i].en]++;
f[a[i].st]=Max(f[a[i].st],a[i].arrive);
}
for(i=2;i<=n;++i) sum[i]+=sum[i-1];
time[1]=0;
for(i=2;i<=n;++i) time[i]=Max(time[i-1],f[i-1])+dist[i-1];
for(i=1;i<=m;++i) ans+=time[a[i].en]-a[i].arrive;
while(k) {
g[n]=n;
g[n-1]=n;
for(i=n-2;i>=1;i--) {
if(time[i+1]>f[i+1])g[i]=g[i+1];
else g[i]=i+1;
}
int MAX=0,l;
for(i=1;i<=n;++i) {
if(sum[g[i]]-sum[i]>MAX&&dist[i]) {
MAX=sum[g[i]]-sum[i];
l=i;
}
}
if(!MAX) break;
ans-=MAX;
dist[l]--;
k--;
time[1]=0;
for(i=2;i<=n;++i) time[i]=Max(time[i-1],f[i-1])+dist[i-1];
}
printf("%d",ans);
}
int main(){
#ifdef READ
freopen("bus.in","r",stdin);
freopen("bus.ans","w",stdout);
#endif
init();
}
T3 修筑绿化带
二维线段树
题目:
【问题描述】
为了增添公园的景致,现在需要在公园中修筑一个花坛,同时在画坛四周修建一片绿化带,让花坛被绿化带围起来。
如果把公园看成一个M*N的矩形,那么花坛可以看成一个C*D的矩形,绿化带和花坛一起可以看成一个A*B的矩形。
如果将花园中的每一块土地的“肥沃度”定义为该块土地上每一个小块肥沃度之和,那么,
绿化带的肥沃度=A*B块的肥沃度-C*D块的肥沃度
为了使得绿化带的生长得旺盛,我们希望绿化带的肥沃度最大。
【输入】:
第一行有6个正整数M,N,A,B,C,D
接下来一个M*N的数字矩阵,其中矩阵的第i行j列元素为一个整数Xij,表示该花园的第i行第j列的土地“肥沃度”。
【输出】:
一个正整数,表示绿化带的最大肥沃程度。
【输入输出样例】
parterre.in
4 5 4 4 2 2
20 19 18 17 16
15 14 13 12 11
10 9 8 7 6
5 4 3 2 1
parterre.out
132
【数据范围】
30%的数据,1<=M,N<=50
100%的数据,1<=M,N<=1000,1<=A<=M,1<=B<=N,1<=C<=A-2,1<=D<=B-2,1<=“肥沃度”<=100
一个大线段树,每一个节点是一颗小线段树,大线段树存的是每一行的情况,其每一个节点表示每一列的情况,具体看代码(PS:代码是HZOI第一大神stdafx主页stdafx.tk原创,我只是借鉴一下模板)
#define MAXN 1010UL
#define lson l,mid,t
#define rson mid+1,r,t|1
#include <cstdio>
int n,m,m4,A,B,C,D,s[MAXN][MAXN],ml,mr,Ans,posl,posr;
inline int MIN(int a,int b){
return a<b?a:b;
}
inline int in(){
int x=0,c=getchar();
while(c<'0'||c>'9') c=getchar();
for(;c>='0'&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+c-48;
return x;
}
inline int Martix(int x,int y){
if(x<C||y<D) return 0x1fffffff;
return s[x][y]-s[x-C][y]-s[x][y-D]+s[x-C][y-D];
}
inline int Martix_Biger(int x,int y){
return s[x][y]-s[x-A][y]-s[x][y-B]+s[x-A][y-B];
}
struct SegTree{
int id,tr[MAXN<<2],pl,pr;
inline void s_Build(int l,int r,int rt){
if(l==r){tr[rt]=Martix(id,l);return;}
int mid=(l+r)>>1,t=rt<<1;
s_Build(lson);s_Build(rson);
tr[rt]=MIN(tr[t],tr[t|1]);
return;
}
inline int s_QUERY(int l,int r){
pl=l,pr=r;return s_Query(1,m,1);
}
inline int s_Query(int l,int r,int rt){
if(pl<=l&&pr>=r) return tr[rt];
int mid=(l+r)>>1,t=rt<<1,rtn=0x2fffffff;
if(pl<=mid) rtn=s_Query(lson);
if(pr>mid){
int temp=s_Query(rson);
if(temp<rtn) rtn=temp;
}
return rtn;
}
friend SegTree operator + (const SegTree a,const SegTree b){
SegTree c;
for(int i=1;i<=m4;i++) c.tr[i]=MIN(a.tr[i],b.tr[i]);
return c;
}
}tree[MAXN<<2];
inline void Build(int l,int r,int rt){
if(l==r){
tree[rt].id=l;
tree[rt].s_Build(1,m,1);
return;
}
int mid=(l+r)>>1,t=rt<<1;
Build(lson);Build(rson);
tree[rt]=tree[t]+tree[t|1];
return;
}
inline int Query(int l,int r,int rt){
if(posl<=l&&posr>=r){
return tree[rt].s_QUERY(ml,mr);
}
int mid=(l+r)>>1,t=rt<<1,rtn=0x2fffffff;
if(posl<=mid) rtn=Query(lson);
if(posr>mid){
int temp=Query(rson);
if(temp<rtn) rtn=temp;
}
return rtn;
}
int main(){
scanf("%d%d%d%d%d%d",&n,&m,&A,&B,&C,&D);m4=m*3;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) s[i][j]=in(),s[i][j]+=s[i][j-1];
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) s[i][j]+=s[i-1][j];
Build(1,n,1);
for(int i=A;i<=n;i++){
posl=i-A+1+C;posr=i-1;
for(int j=B;j<=m;j++){
ml=j-B+1+D;mr=j-1;
int p=Martix_Biger(i,j)-Query(1,n,1);
if(p>Ans) Ans=p;
}
}
printf("%d\n",Ans);
return 0;
}
T4 Problem C
DP
题目:
题目描述
给n个人安排座位,先给每个人一个1~n的编号,设第i个人的编号为ai(不同人的编号可以相同),接着从第一个人开始,大家依次入座,第i个人来了以后尝试坐到ai,如果ai被占据了,就尝试ai+1,ai+1也被占据了的话就尝试ai+2,……,如果一直尝试到第n个都不行,该安排方案就不合法。然而有m个人的编号已经确定(他们或许贿赂了你的上司...),你只能安排剩下的人的编号,求有多少种合法的安排方案。由于答案可能很大,只需输出其除以M后的余数即可。
输入格式:
第一行一个整数T,表示数据组数
对于每组数据,第一行有三个整数,分别表示n、m、M
若m不为0,则接下来一行有m对整数,p1、q1,p2、q2 ,…, pm、qm,其中第i对整数pi、qi表示第pi个人的编号必须为qi
输出格式:
对于每组数据输出一行,若是有解则输出YES,后跟一个整数表示方案数modM,注意,YES和数之间只有一个空格,否则输出NO
样例输入:
2
4 3 10
1 2 2 1 3 1
10 3 8882
7 9 2 9 5 10
样例输出:
YES 4
NO
数据范围:
30%的数据满足:1≤n≤10
100%的数据满足:1≤T≤10,1≤n≤300,0≤m≤n,2≤M≤109,1≤pi、qi≤n 且保证pi互不相同。
f[i][j]表示i~n中有j个编号已经确定,倒着更新f[i][j]+=f[i+1][j-k]*C(j,k)%M k∈[0,j]
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int MAXN=305;
int T,n,m,mod;
LL f[MAXN][MAXN],c[MAXN][MAXN],s[MAXN];
bool init(){
int x,y,i;
scanf("%d%d%d",&n,&m,&mod);
for(i=1;i<=m;++i) {
scanf("%d%d",&x,&y);
s[y]++;
}
for(i=n;i>0;--i) {
s[i]+=s[i+1];
if(s[i]>n-i+1)
return 0;
}
return 1;
}
void C() {
int i,j;
c[0][0]=0;
c[0][1]=1;
for(i=1;i<=n+2;++i)
for(j=1;j<=i;++j)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
LL dp() {
int i,j,k;
f[n+1][0]=1;
for(i=n;i>0;i--)
for(j=0;j<=n-i+1-s[i];++j)
for(k=0;k<=j;++k){
f[i][j]+=f[i+1][j-k]%mod*c[j+1][k+1]%mod;
f[i][j]%=mod;
}
return f[1][n-s[1]];
}
void clr() {
memset(f,0,sizeof(f));
memset(c,0,sizeof(c));
memset(s,0,sizeof(s));
}
int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%d",&T);
while(T--) {
if(init()==0){
puts("NO");
}
else {
C();
printf("YES %lld\n",dp());
}
clr();
}
return 0;
}