NOIP2012——DAY2
1、同余方程
【题目分析】
其实也不用分析了,这道题是作为我们的数论入门题来练的。如果在考场上碰到这种恶心的数学题,不管敲得对敲不对,反正一定要把暴力先敲好。
http://blog.csdn.net/ycdfhhc/article/details/44260687
作为一个蒟蒻,想解释但还是心有余而力不足啊。不懂的小伙伴自行学习,早就懂的小伙伴也可以温习一下。
【代码】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll ex_gcd(ll a,ll b,ll &x,ll &y){
ll d=a;
if(b){
d=ex_gcd(b,a%b,y,x);
y-=a/b*x;
}else x=1,y=0;
return d;
}
int main(){
ll a,b,x,y;
cin>>a>>b;
int ans=ex_gcd(a,b,x,y);
while(x<=0)x+=b;
cout<<x<<endl;
return 0;
}
2、借教室
【题目分析】
都DAY2了,还能不能让我愉快地敲一个暴力?
不多说,45分暴力代码先给出。
【45分代码】
#define M 1000005
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int cnt[M];
int main(){
int i,j,n,m;
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)scanf("%d",&cnt[i]);
for(i=1;i<=m;i++){
int d,l,r;
scanf("%d %d %d",&d,&l,&r);
for(j=l;j<=r;j++){
cnt[j]-=d;
if(cnt[j]<0)break;
}if(j!=r+1)break;
}
if(i==m+1)puts("0");
else puts("-1"),printf("%d\n",i);
return 0;
}
枚举->贪心,貌似都不行。
我们的目标是完成所有订单。
题目里虽然没有说最大、最小,之类的话,只说了,“如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配”,如果这一份订单如果无法完成,那么我们假设的可以完成所有订单这一目标也一定无法完成。
如果先定一个小目标,看他能不能完成。如果小目标不能完成,大目标也一定不能完成。所以,具有单调性。
思路就变成了二分答案。
同时,在假设当前的目标能够完成的时候,一个一个减太慢了,我们学过差分前缀和(也就是刷漆),将每次查询的复杂度降到了O(n)。
复杂度O(n*logn)。
【代码】
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define M 1000005
#include<iostream>
#include<algorithm>
using namespace std;
void Rd(int &res){
char c;res=0;
while(c=getchar(),!isdigit(c));
do{res=(res<<3)+(res<<1)+(c^48);}while(c=getchar(),isdigit(c));
}
struct quertion{int l,r,d;}q[M];
int n,m,ans=0,a[M];
long long b[M];
bool chk(int x){
bool f=1;
long long sum=0;
memset(b,0,sizeof(b));
for(int i=1;i<=x;i++){
b[q[i].l]+=q[i].d;
b[q[i].r+1]-=q[i].d;
}
for(int i=1;i<=n;i++){
sum+=b[i];
if(sum>a[i])return 0;
}return 1;
}
int main(){
int i;
Rd(n),Rd(m);
for(i=1;i<=n;i++)Rd(a[i]);
for(i=1;i<=m;i++)Rd(q[i].d),Rd(q[i].l),Rd(q[i].r);
int l=1,r=m;
while(l<=r){
int mid=l+r>>1;
if(!chk(mid))ans=mid,r=mid-1;
else l=mid+1;
}
if(!ans)puts("0");
else printf("-1\n%d\n",ans);
return 0;
}
天啊撸,刚打好的疫情控制题解没了,都怪一个叫yahong的变态!!
3、疫情控制
【题目分析】
从枚举军队最后的状态入手,可以水到20分。
需要将有限的军队最大的发挥它们的作用,就要不断向上攀爬,使他能满足更多的边疆城市,这是贪心的思想。
从常识中可以知道,若是疫情被能控制,时间越长,更有可能。而我们不可能按照时间的推移来检验该时间是否可行(显然超时),又有“请问最少需要多少个小时才能控制疫情”,得出二分答案的做法。
但是首都的儿子中有些点是本来就没有军队的,所以将军队又分为了需要翻过首都支援的和留在原地的。而我们又希望剩余时间更多的来支援,这还是贪心的思想。
接下来就是如何实现的问题了。
【代码】
#define M 50005
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define oo 1e15
using namespace std;
void Rd(int &res){
res=0;char c;
while(c=getchar(),!isdigit(c));
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),isdigit(c));
}
struct node{int v,w,nxt;}st[M<<1];
int fa[M],army[M],head[M],rest[M];
int n,m,etop=0;
bool mk[M];
ll dis[M];
void add_edge(int u,int v,int w){
st[++etop]=(node){v,w,head[u]},head[u]=etop;
st[++etop]=(node){u,w,head[v]},head[v]=etop;
}
void dfs(int u,int pre,ll d){
dis[u]=d,fa[u]=pre;
for(int j=head[u];~j;j=st[j].nxt){
node now=st[j];
if(now.v!=pre)dfs(now.v,u,d+now.w);
}
}
struct cmp{bool operator()(int &a,int &b)const{return dis[a]<dis[b];}};
bool check(int u,int pre,bool flag){
if(flag)return 1;
bool f=0;
for(int j=head[u];~j;j=st[j].nxt){
int v=st[j].v;
if(v==pre)continue;
f=1;
if(!check(v,u,flag|mk[v]))return 0;
}return f;
}
bool check(ll T){
memset(mk,0,sizeof(mk));
memset(rest,-1,sizeof(rest));
priority_queue<int>q1;
priority_queue<int,vector<int>,cmp>q2;
for(int i=1;i<=m;i++){
int u=army[i];
while(fa[u]&&fa[u]!=1&&dis[army[i]]-dis[fa[u]]<=T)u=fa[u];
if(fa[u]!=1||T<dis[army[i]])mk[u]=1;
else{
int v=T-dis[army[i]];
if(rest[u]==-1)rest[u]=v;
else{
if(rest[u]>v)swap(rest[u],v);
q1.push(v);
}
}
}
for(int j=head[1];~j;j=st[j].nxt){
int v=st[j].v;
if((~rest[v])&&(check(v,1,mk[v])||rest[v]>=dis[v]))q1.push(rest[v]);
else if(~rest[v])mk[v]=1;
}
for(int j=head[1];~j;j=st[j].nxt){
int v=st[j].v;
if(!check(v,1,mk[v]))q2.push(v);
}
while(!q1.empty()&&!q2.empty()){
int v=q2.top();
if(q1.top()>=dis[v]){
mk[v]=1;
q1.pop();
q2.pop();
}else q1.pop();
}return check(1,0,0);
}
int main(){
Rd(n);
int cnt=0;
memset(head,-1,sizeof(head));
for(int i=1,u,v,w;i<n;i++){
Rd(u),Rd(v),Rd(w);
if(u==1||v==1)cnt++;
add_edge(u,v,w);
}Rd(m);
for(int i=1;i<=m;i++)Rd(army[i]);
if(cnt>m){puts("-1");return 0;}
dfs(1,0,0);
int l=0;
ll r=oo;
ll ans=-1;
while(l<=r){
int md=l+r>>1;
if(check(md))ans=md,r=md-1;
else l=md+1;
}cout<<ans<<endl;
return 0;
}
虽然能过,但是有点慢,是因为想偷点懒,所以在向上的时候没有“跳”。
【思路】
1、写过了。
2、枚举->二分
3、枚举->贪心->二分答案
总的来说,如果有恶心的数学题,还是乖乖地先去敲出暴力再去想正解吧,虽然说可能性并不大。同时二分答案也是解题的一个思路之一,在想问题的时候一定要往上面去想一想,套一套。还有就是第三题,有了思路并不代表有了一切,最终还是要把它实现出来啊。