20151008题解
番外篇:考试4道题本来认为上200+有很多人,结果老师高能,说最后一道题太简单,把小数据都删了QAQ。。
(我就是靠小数据谋生的啊),更高能的是,老师把大数据重复了两份,还把100分的题设置成了200。。。。
加上第二道题出乎意料得了70
出纳员的雇佣
【题目描述】
德黑兰的一家每天24小时营业的超市,需要一批出纳员来满足它的需要。超市经理雇佣你来帮他解决他的问题——超市在每天的不同时段需要不同数目的出纳员(例如:午夜时只需一小批,而下午则需要很多)来为顾客提供优质服务。他希望雇佣最少数目的出纳员。
经理已经提供你一天的每一小时需要出纳员的最少数量——R0, R1,..., R23。R0表示从午夜到上午1:00需要出纳员的最少数目,R1表示上午1:00到2:00之间需要的,等等。每一天,这些数据都是相同的。有N人申请这项工作,每个申请者I在没24小时中,从一个特定的时刻开始连续工作恰好8小时,定义tI(0≤tI≤23)为上面提到的开始时刻。也就是说,如果第I个申请者被录取,他(她)将从tI时刻开始连续工作8小时。
你将编写一个程序,输入RI(I = 0..23)和tI (I =1..N),它们都是非负整数,计算为满足上述限制需要雇佣的最少出纳员数目。在每一时刻可以有比对应的RI更多的出纳员在工作。
【输入格式】
输入文件的第一行为测试点个数(<= 20)。每组测试数据的第一行为24个整数表示R0,R1,..., R23(RI≤1000)。接下来一行是N,表示申请者数目(0≤N≤1000),接下来每行包含一个整数tI (0≤tI≤23)。两组测试数据之间没有空行。
【输出格式】
对于每个测试点,输出只有一行,包含一个整数,表示需要出纳员的最少数目。如果无解,你应当输出“No Solution”。
【样例输入】
1
1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 1
5
0
23
22
1
10
【样例输出】
1
题解
我一开始想了,觉得是个贪心,后来想想没有贪心标准,贪心时还得不断做出决策
于是想到了DP,DP觉得数据不大,应该就是动归,结果。。。。。连状态数组开几维都想不出来。其实我们在想状态数组时亦然,一定要想它表示的是什么意义,表示出的意义在程序实际运行时是否符合DP的思想。后来这个题打了个2^n的暴力,匆匆交了上去
旁边某位神犇直接输出NO Solution我也是服了,这多组测试数据。。。。。
后来这个题是考试改的四道题中倒数第二道敲代码的,很疲惫,很累,非常不愿意打
但是最后还是坚持下来了,打了2KB。
正解:
------差分约束
你肯定不相信,这个和图论有个毛关系,但是可以发现,题目是给定的某一时刻的消息
但是试图描述的却是某一时间段的信息 令dis[x]表示x时间段一共至少雇佣多少员工
也就是前缀和的形式,实在比较难想,所以看不懂的oier就忽略这一段
下面进入差分阶段 定义ned数组 表示第x小时至少需要多少员工,也就是题目里的R
定义hav[x]表示第x小时有多少人申请工作.边二分,边构图spfa跑最短路,
二分什么---我们二分的是lim即欲招员工的个数
对于题目中给定的约束条件构图
(1)0<=dis[x]-dis[x-1]<=hav[x]//雇佣的人数小于等于申请的人数但不能为负数
(2)dis[x]-dis[x-8]>=ned[x]//显然,数组下表非负,当x>=8时,此方程成立
//对于这个方程意义的解释为,在第x小时可以干活的人只与x-8小时后的有关系
(3)dis[x-8+24]-dis[i]<=lim-ned[x];//当x<8时,就用这个方程 你会发现样例是通宵上班的
(4)dis[24]-dis[0]>=lim//最后24小时内雇佣的人应大于等于lim个人
这样构图得
dis[24]-dis[0]>=lim,发现lim是变量,那就二分呗,
变形得dis[0]-dis[24]<=-lim再变形得 d[0]<=d[24]-lim;
即让dis[0]最小那不就是求24到0的最短路 spfa秒之...
还要注意:我给的代码是从1~24 题目描述是从0~23
.............那就这样吧
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN = 1010;
int hav[MAXN],ned[MAXN],f[MAXN];
bool vis[MAXN];
int c[MAXN];
int T,ans=-1,n;
struct Edge{
int next,to,data;
}edge[MAXN*MAXN];
int edgenum;
int head[MAXN];
inline void merge(int x,int y,int data){
edgenum++; edge[edgenum].next=head[x];
edge[edgenum].to=y; edge[edgenum].data=data; head[x]=edgenum;
}
inline bool spfa(int lim){
queue<int> q;
vis[24]=1;
f[24]=0;
c[24]=1;
q.push(24);
while(!q.empty()){
int fr=q.front();
q.pop();
vis[fr]=0;
if(c[fr]>25) return false;
for(int i=head[fr];i!=-1;i=edge[i].next){
int t=edge[i].to;
if(f[t]>f[fr]+edge[i].data){
f[t]=f[fr]+edge[i].data;
if(!vis[t]){
vis[t]=1;
c[t]++;
q.push(t);
}
}
}
}
if(f[0]==-lim)
return 1;
return 0;
}
inline bool check(int lim){
memset(head,-1,sizeof head);
memset(f,25,sizeof f);
memset(vis,0,sizeof vis);
memset(c,0,sizeof c);
edgenum=0;
for(int i=1;i<=24;i++){
merge(i-1,i,hav[i]);
merge(i,i-1,0);
}
for(int i=1;i<8;i++)
merge(i,24+i-8,lim-ned[i]);
for(int i=8;i<=24;i++)
merge(i,i-8,-ned[i]);
merge(24,0,-lim);
if(spfa(lim))
return 1;
return 0;
}
int main(void){
freopen("ployment.in","r",stdin);
freopen("ployment.out","w",stdout);
scanf("%d",&T);
while(T){
memset(hav,0,sizeof hav);
ans=-1;
T--;
for(int i=1;i<=24;i++)
scanf("%d",&ned[i]);
scanf("%d",&n);
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x); x++;
hav[x]++;
}
int l=0,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(!check(mid))
l=mid+1;
else{
ans=mid; r=mid-1;
}
}
if(ans==-1)
printf("No Solution\n");
else
printf("%d\n",ans);
}
return 0;
}
飞行路线--堆优化spfa
Description
Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在n个城市设有业务,设这些城市分别标记为0到n-1,一共有m种航线,每种航线连接两个城市,并且航线有一定的价格。Alice和Bob现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多k种航线上搭乘飞机。那么Alice和Bob这次出行最少花费多少?
Input
数据的第一行有三个整数,n,m,k,分别表示城市数,航线数和免费乘坐次数。
第二行有两个整数,s,t,分别表示他们出行的起点城市编号和终点城市编号。(0<=s,t<n)
接下来有m行,每行三个整数,a,b,c,表示存在一种航线,能从城市a到达城市b,或从城市b到达城市a,价格为c。(0<=a,b<n,a与b不相等,0<=c<=1000)
Output
只有一行,包含一个整数,为最少花费。
Sample Input
5 6 1
0 4
0 1 5
1 2 5
2 3 5
3 4 5
2 3 3
0 2 100
Sample Output
8
HINT
对于30%的数据,2<=n<=50,1<=m<=300,k=0;
对于50%的数据,2<=n<=600,1<=m<=6000,0<=k<=1;
对于100%的数据,2<=n<=10000,1<=m<=50000,0<=k<=10.
这个题..,我也是无语了,一上来就敲了个裸的二位spfa,结果得了60 QAQ(话说spfa没听说过堆优化)...
Spfa对稀疏图的确强大,但由于稠密图常数很大,和迪杰斯特拉差不多,spfa肯定跪
因此想到了堆优化
那就这样吧
#include<cstdio>
#include<cstring>
#include<queue>
#define MAXN 100540
#define MAXC 10101
#define max(a,b) a>b?a:b
#define min(a,b) a<b?a:b
using std::priority_queue;
inline int in(void){
char c=getchar();int x=0;
while(c<'0'||c>'9') c=getchar();
for(;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
return x;
}
int edgenum,n,m,k;
struct Edge{int next,to,data;}edge[MAXN];
struct node{
int fir,sec,data;
friend bool operator < (node a,node b){return a.data>b.data;}
};
int head[MAXC];
bool vis[MAXC][15];
int f[MAXC][15];
int s,t,ans=0x7fffffff;
inline void add(int x,int y,int data){
edgenum++;
edge[edgenum].next=head[x];
edge[edgenum].to=y;
edge[edgenum].data=data;
head[x]=edgenum;
return ;
}
inline node make_node(int x,int y){
node now;
now.fir=x,now.sec=y,now.data=f[x][y];
return now;
}
inline void spfa(int s){
memset(f,0x3f,sizeof f);
memset(vis,0,sizeof vis);
priority_queue<node> q;
vis[s][0]=true;
f[s][0]=0;
q.push(make_node(s,0));
while(!q.empty()){
int fr_fir=q.top().fir;
int fr_sec=q.top().sec;
vis[fr_fir][fr_sec]=0;
q.pop();
for(int i=head[fr_fir];i!=-1;i=edge[i].next)
if(f[edge[i].to][fr_sec]>f[fr_fir][fr_sec]+edge[i].data){
f[edge[i].to][fr_sec]=f[fr_fir][fr_sec]+edge[i].data;
if(!vis[edge[i].to][fr_sec]){
vis[edge[i].to][fr_sec]=1;
q.push(make_node(edge[i].to,fr_sec));
}
}
if(fr_sec+1<=k){
for(int i=head[fr_fir];i!=-1;i=edge[i].next)
if(f[edge[i].to][fr_sec+1]>f[fr_fir][fr_sec]){
f[edge[i].to][fr_sec+1]=f[fr_fir][fr_sec];
if(!vis[edge[i].to][fr_sec+1]){
vis[edge[i].to][fr_sec+1]=1;
q.push(make_node(edge[i].to,fr_sec+1));
}
}
}
}
return ;
}
int main(){
freopen("meg.in","r",stdin);
freopen("meg.out","w",stdout);
memset(head,-1,sizeof head);
n=in(),m=in(),k=in(),s=in(),t=in();
for(int i=1,x,y,ch;i<=m;i++) x=in(),y=in(),ch=in(),add(x,y,ch),add(y,x,ch);
spfa(s);
for(int i=0;i<=k;i++)
ans=min(f[t][i],ans);
printf("%d",ans);
return 0;
}
NOIP借教室--segment_tree| |二分答案
【题目描述】
在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。
面对海量租借教室的信息,我们自然希望编程解决这个问题。
我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份 订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租 借教室(包括第sj天和第tj天),每天需要租借dj个教室。
我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申 请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。
现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。
【输入格式】
第一行包含两个正整数n,m,表示天数和订单的数量。
第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。
接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在 第几天。
每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。
【输出格式】
如果所有订单均可满足,则输出只有一行,包含一个整数 0。否则(订单无法完全满足) 输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。
【样例输入】
4 3
2 5 4 3
2 1 3
3 2 4
4 2 4
【样例输出】
-1
2
【输入输出样例说明】
第 1 份订单满足后,4 天剩余的教室数分别为 0,3,2,3。第 2 份订单要求第 2 天到 第 4 天每天提供 3 个教室,而第 3 天剩余的教室数为 2,因此无法满足。分配停止,通知第 2 个申请人修改订单。
【数据范围】
对于 10%的数据,有1 ≤ n,m ≤ 10;
对于 30%的数据,有1 ≤ n,m ≤ 1000;
对于 70%的数据,有1 ≤ n,m ≤ 10^5;
对于 100%的数据,有1 ≤ n,m ≤ 10^6,0 ≤ ri,dj ≤ 10^9,1 ≤ sj ≤ tj ≤ n。
数据结构大神都会想这是一个裸的线段树操作.......可是NOIP官方数据应该可以将带lazytag的线段树卡掉三个点.....CCF真是..无语了
先让我吐槽一下.考试先敲暴力,敲完暴力打线段树发现全跪了,因为询问区间最小值改成求和了...样例竟然可以过!!!!!!!!!
线段树就这么飘过70
再有就是二分答案
二分订单数量,判断一下前mid个订单是否可以。具体操作是前缀和处理,即每读入一个订单就在起始天+要借的房间数量,在结束天的下一天减去要借的房间数量。(#)
然后比较每一天的前缀和与本天总共的房间数的大小,如果房间数<前缀和,就说明前mid个订单有问题,向前二分;否则就向后二分。
注意left和right的处理
上代码
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 1001010UL
int ans;
int a[MAXN],sum[MAXN],d[MAXN],x[MAXN],y[MAXN];
int n,m,l,r;
inline bool check(int mid){
memset(sum,0,sizeof sum);
for(int i=1;i<=mid;i++){sum[x[i]]+=d[i],sum[y[i]+1]-=d[i];}
int tot=0;
for(int i=1;i<=n;i++){
tot+=sum[i];
if(tot>a[i])
return false;
}
return true;
}
int main(){
freopen("classroom.in","r",stdin);
freopen("classroom.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&d[i],&x[i],&y[i]);
l=1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(!check(mid))
ans=mid,r=mid-1;
else l=mid+1;}
if(!ans)
printf("0");
else
printf("-1\n%d",ans);
return 0;
}
每日一语 生活就是让你做一些你不情愿的事情;