10.30 NFLS-NOIP模拟赛 解题报告
总结:今天去了NOIP模拟赛,其实是几道USACO的经典的题目,第一题和最后一题都有思路,第二题是我一开始写了个spfa,写了一半中途发现应该是矩阵乘法,然后没做完,然后就没有然后了!第二题的暴力都没码QAQ 现在我来写解题报告了,有点饿了QAQ、、
第一题
题目 1: 架设电话线 [Jeffrey Wang, 2007] 最近,Farmer John的奶牛们越来越不满于牛棚里一塌糊涂的电话服务,于 是,她们要求FJ把那些老旧的电话线换成性能更好的新电话线。新的电话线架设 在已有的N(2 <= N <= 100,000)根电话线杆上,第i根电话线杆的高度为 height_i米(1 <= height_i <= 100)。电话线总是从一根电话线杆的顶端被引到 相邻的那根的顶端,如果这两根电话线杆的高度不同,那么FJ就必须为此支付 C*电话线杆高度差(1 <= C <= 100)的费用。当然,你不能移动电话线杆,只能 按原有的顺序在相邻杆间架设电话线。 Farmer John认为,加高某些电话线杆能减少架设电话线的总花费,尽管这 项工作也需要支出一定的费用。更准确地,如果他把一根电话线杆加高X米的话 ,他得为此付出X^2的费用。 请你帮Farmer John计算一下,如果合理地进行这两种工作,他最少要在这 个电话线改造工程上花多少钱。 程序名: telewire 输入格式: * 第1行: 2个用空格隔开的整数:N和C * 第2..N+1行: 第i+1行仅有一个整数:height_i 输入样例 (telewire.in): 5 2 2 3 5 1 4 输入说明: 一共有5根电话线杆,在杆间拉电话线的费用是每米高度差$2。在改造之前 ,电话线杆的高度依次为2,3,5,1,4米。 输出样例: * 第1行: 输出Farmer John完成电话线改造工程所需要的最小花费 输出样例 (telewire.out): 15 输出说明: 最好的改造方法是:Farmer John把第一根电话线杆加高1米,把第四根加 高 2米,使得它们的高度依次为3,3,5,3,4米。这样花在加高电线杆上的钱是 $5 。此时,拉电话线的费用为$2*(0+2+2+1) = $10,总花费为$15。
题解
一开始考场上看到这题我惊喜做过的,然后发现呵呵,原来USACO有两题架设电话线,不过没关系感觉这道题DP能水一点分数,我就一开始顺手码了个暴力的DP,最后是90分,TLE了一个点。【P.S点我去,我回家i7四核都跑不出90分,这成绩真的没问题?】我暴力DP的思路就是f[i][j]表示第i个电线杆高度为j时,此电线杆和之前所有电线杆还有架线的最小花费。这样时间复杂度是O(nh^2)。从估计来看,欸?明明应该是水过啊!好吧,复杂度多了一个0,水过个P啊!好吧,来讲一讲正解,正解的复杂度是O(nh)的,这样刚好比超时少一个0.f[i][j]表示前i个,为j的个数,f[i][j]=f[i-1][p]+abs(p-j)*c+(j-a[i])*(j-a[i]);然后记录两个数组low,high,转移。
代码
1 /*ZZ*/ 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int Maxn=100000; 6 const int Maxh=100; 7 int n,c; 8 int hi[Maxn+10]; 9 int f[Maxn+10][Maxh+10]; 10 inline int remin(int a,int b){ 11 if (a<b) return a; 12 return b; 13 } 14 inline int abs(int x){ 15 if (x<0) return -x; 16 return x; 17 } 18 int main(){ 19 freopen("telewire.in","r",stdin); 20 freopen("telewire.out","w",stdout); 21 scanf("%d%d",&n,&c); 22 for (int i=1;i<=n;i++) scanf("%d",&hi[i]); 23 memset(f,127,sizeof(f)); 24 for (int i=hi[1];i<=Maxh;i++) f[1][i]=(i-hi[1])*(i-hi[1]); 25 for (int i=2;i<=n;i++){ 26 for (int h=hi[i];h<=Maxh;h++){ 27 for (int k=Maxh;k>=1;k--){ 28 if (f[i-1][k]==f[0][0]) break; 29 f[i][h]=remin(f[i][h],f[i-1][k]+abs(k-h)*c+(h-hi[i])*(h-hi[i])); 30 } 31 } 32 } 33 int Ans=2147483647; 34 for (int h=hi[n];h<=Maxh;h++){ 35 Ans=remin(Ans,f[n][h]); 36 } 37 printf("%d\n",Ans); 38 return 0; 39 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 int f[1005]; 6 int dp[1005]; 7 int a[100005]; 8 int low[1005]; 9 int high[1005]; 10 int c; 11 int n; 12 int p; 13 int ans=-1; 14 int abs(int x) 15 { 16 if(x>0)return x; 17 return -x; 18 } 19 int sum; 20 int main() 21 { 22 freopen("telewire.in","r",stdin); 23 freopen("telewire.out","w",stdout); 24 scanf("%d%d",&n,&c); 25 for(int i=1;i<=n;i++) 26 { 27 scanf("%d",&a[i]); 28 p=max(p,a[i]); 29 if(i>1)sum+=c*abs(a[i]-a[i-1]); 30 } 31 sum=sum+5; 32 for(int i=0;i<a[1];i++) 33 dp[i]=sum; 34 for(int i=a[1];i<=p;i++) 35 dp[i]=(i-a[1])*(i-a[1]); 36 for(int i=2;i<=n;i++) 37 { 38 for(int j=0;j<=100;j++) 39 { 40 f[j]=dp[j]; 41 dp[j]=low[j]=high[j]=sum; 42 } 43 low[0]=f[0]; 44 for(int j=1;j<=100;j++) 45 { 46 low[j]=min(low[j-1],f[j]-c*j); 47 } 48 high[100]=f[100]+100*c; 49 for(int j=99;j>=0;j--) 50 { 51 high[j]=min(high[j+1],f[j]+c*j); 52 } 53 for(int j=a[i];j<=100;j++) 54 { 55 dp[j]=min(high[j]-c*j,low[j]+c*j)+(j-a[i])*(j-a[i]); 56 // cout<<i<<" "<<j<<" "<<dp[j]<<endl; 57 } 58 } 59 for(int i=a[n];i<=100;i++) 60 if(ans==-1 || dp[i]<ans)ans=dp[i]; 61 printf("%d\n",ans); 62 return 0; 63 }
第二题
题目 2: 奶牛接力 [Erik Bernhardsson, 2003] FJ的N(2 <= N <= 1,000,000)头奶牛选择了接力跑作为她们的日常锻炼项目 。至于进行接力跑的地点,自然是在牧场中现有的T(2 <= T <= 100)条跑道 上。 农场上的跑道有一些交汇点,每条跑道都连结了两个不同的交汇点I1_i和 I2_i(1 <= I1_i <= 1,000; 1 <= I2_i <= 1,000)。每个交汇点都是至少两条跑 道的端点。奶牛们知道每条跑道的长度length_i(1 <= length_i <= 1,000),以 及每条跑道连结的交汇点的编号。并且,没有哪两个交汇点由两条不同的跑道直 接相连。你可以认为这些交汇点和跑道构成了一张图。 为了完成一场接力跑,所有N头奶牛在跑步开始之前都要站在某个交汇点上 (有些交汇点上可能站着不只1头奶牛)。当然,她们的站位要保证她们能够将 接力棒顺次传递,并且最后持棒的奶牛要停在预设的终点。 你的任务是,写一个程序,计算在接力跑的起点(S)和终点(E)确定的情况下 ,奶牛们跑步路径可能的最小总长度。显然,这条路径必须恰好经过N条跑道。 程序名: relays 输入格式: * 第1行: 4个用空格隔开的整数:N,T,S,以及E * 第2..T+1行: 第i+1为3个以空格隔开的整数:length_i,I1_i,以及I2_i, 描 述了第i条跑道。 输入样例 (relays.in): 2 6 6 4 11 4 6 4 4 8 8 4 9 6 6 8 2 6 9 3 8 9 输出格式: * 第1行: 输出1个正整数,表示起点为S、终点为E,并且恰好经过N条跑道的路 径的最小长度 输出样例 (relays.out): 10
题解
这道题我当时真的是一点思路都没有啊,感觉只能搜索,但是点那么多边只有100真的很可疑啊!我先写了一个spfa,然后感觉不太对,又写了矩阵乘法!其实应该是正解了,但是我没调完就结束了QAQ,我就再次贴上网上的一个矩阵乘法的思路:题目求i,j之间边数恰为N的最短路径(边可以重复走),我们知道线性代数中有:01邻接矩阵A的K次方C=A^K,C[i][j]表示i点到j点正好经过K条边的路径数。而floyd则是每次使用一个中间点k去更新i,j之间的距离,那么更新成功表示i,j之间恰有一个点k时的最短路,如果做N次floyd那么不就是i,j之间借助N个点时的最短路了?考虑当a[i][k]+a[k][j]<c[i][j]的时候,c[i][j]=a[i][k]+a[k][j],这样c[i][j]保存了i,j之间有一个点的最短路,第二次将c[i][j]拷贝回到a[i][j]当中,并将c[i][j]重新置为INF,再做一次,则是在原来的基础上在i,j之间再用一个点k来松弛,这时候i,j之间实际上已经是两个点了,之后重复这么做就好了,可以利用二进制加速。其实,我们当时考试有人用暴力瞎搞搞也就弄出来了QAQ这、、、、、赫赫!
代码
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <set> 5 using namespace std ; 6 typedef long long LL ; 7 const double eps(1e-10) ; 8 const int inf(0x3f3f3f3f) ; 9 int n,t,s,e; 10 struct N{ 11 int x,y,z; 12 }a[105]; 13 int to[1005]; 14 set<int> S; 15 int siz; 16 int mul[205][205]; 17 int map[205][205]; 18 void p(int a[][205],int b[][205]){ 19 static int c[205][205]; 20 memset(c,0x3f,sizeof c); 21 for(int i=1;i<=siz;i++){ 22 for(int j=1;j<=siz;j++){ 23 for(int k=1;k<=siz;k++){ 24 c[i][j]=min(a[i][k]+b[k][j],c[i][j]); 25 } 26 } 27 } 28 memcpy(a,c,sizeof map); 29 } 30 int main(){ 31 freopen("relays.in","r",stdin) ; 32 freopen("relays.out","w",stdout) ; 33 scanf("%d%d%d%d",&n,&t,&s,&e); 34 S.insert(s);S.insert(e); 35 for(int i=0;i<t;i++){ 36 scanf("%d%d%d",&a[i].z,&a[i].x,&a[i].y); 37 S.insert(a[i].x);S.insert(a[i].y); 38 } 39 for(set<int>::iterator it=S.begin();it!=S.end();++it){ 40 to[*it]=++siz; 41 } 42 memset(map,0x3f,sizeof map); 43 for(int i=0;i<t;i++){ 44 map[to[a[i].y]][to[a[i].x]]=map[to[a[i].x]][to[a[i].y]]=min(map[to[a[i].x]][to[a[i].y]],a[i].z); 45 } 46 memcpy(mul,map,sizeof map); 47 int k=n-1; 48 while(k){ 49 if(k&1) p(map,mul); 50 p(mul,mul); 51 k>>=1; 52 } 53 printf("%d\n",map[to[s]][to[e]]); 54 return 0 ; 55 }
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 int f[2][10005]; 6 struct node 7 { 8 int len; 9 int x; 10 int y; 11 }g[10005]; 12 const int inf=2e9+7; 13 struct srt 14 { 15 int val; 16 int id; 17 }sr[10005]; 18 int cnt,used[10005]; 19 int final[10005]; 20 int main() 21 { 22 freopen("relays.in","r",stdin); 23 freopen("relays.out","w",stdout); 24 int n,m,s,t; 25 scanf("%d%d%d%d",&n,&m,&s,&t); 26 for(int i=1;i<=m;i++) 27 { 28 scanf("%d%d%d",&g[i].len,&g[i].x,&g[i].y); 29 used[g[i].x]=1; 30 used[g[i].y]=1; 31 } 32 for(int i=1;i<=1000;i++) 33 { 34 if(used[i]) 35 { 36 sr[cnt].val=i; 37 sr[cnt].id=cnt; 38 cnt++; 39 } 40 } 41 for(int i=1;i<=cnt;i++) final[sr[i].val]=sr[i].id; 42 for(int i=1;i<=m;i++) 43 { 44 g[i].x=final[g[i].x]+1; 45 g[i].y=final[g[i].y]+1; 46 } 47 int cur=1; 48 s=final[s]+1; 49 t=final[t]+1; 50 for(int i=1;i<=m;i++) f[0][i]=f[1][i]=inf; 51 f[0][s]=0; 52 //离散化 53 for(int i=1;i<=n;i++) 54 { 55 int pre=1-cur; 56 for(int j=1;j<=m;j++) 57 { 58 int start=g[j].x,end=g[j].y,dis=g[j].len; 59 if(f[pre][start]+dis<f[cur][end]) f[cur][end]=f[pre][start]+dis; 60 if(f[pre][end]+dis<f[cur][start]) f[cur][start]=f[pre][end]+dis; 61 } 62 cur=1-cur; 63 for(int j=1;j<=m;j++) f[pre][j]=inf; 64 } 65 printf("%d\n",f[1-cur][t]); 66 //暴力 67 return 0; 68 }
第三题
题目 3: 分配防晒霜 [Russ Cox, 2001] 奶牛们计划着去海滩上享受日光浴。为了避免皮肤被阳光灼伤,所有C(1 <= C <= 2500)头奶牛必须在出门之前在身上抹防晒霜。第i头奶牛适合的最小和最 大的SPF值分别为minSPF_i和maxSPF_i(1 <= minSPF_i <= 1,000; minSPF_i <= maxSPF_i <= 1,000)。如果某头奶牛涂的防晒霜的SPF值过小,那么阳光仍然能 把她的皮肤灼伤;如果防晒霜的SPF值过大,则会使日光浴与躺在屋里睡觉变得 几乎没有差别。 为此,奶牛们准备了一大篮子防晒霜,一共L(1 <= L <= 2500)瓶。第i瓶 防 晒霜的SPF值为SPF_i(1 <= SPF_i <= 1,000)。瓶子的大小也不一定相同,第i 瓶 防晒霜可供cover_i头奶牛使用。当然,每头奶牛只能涂某一个瓶子里的防晒霜 ,而不能把若干个瓶里的混合着用。 请你计算一下,如果使用奶牛们准备的防晒霜,最多有多少奶牛能在不被灼 伤的前提下,享受到日光浴的效果? 程序名: tanning 输入格式: * 第1行: 2个用空格隔开的整数:C和L * 第2..C+1行: 第i+1行给出了适合第i头奶牛的SPF值的范围:minSPF_i以及 maxSPF_i * 第C+2..C+L+1行: 第i+C+1行为了第i瓶防晒霜的参数:SPF_i和cover_i,两个 数间用空格隔开。 输入样例 (tanning.in): 3 2 3 10 2 5 1 5 6 2 4 1 输入说明: 一共有3头奶牛,2瓶防晒霜。3头奶牛适应的SPF值分别为3..10,2..5,以 及1..5。2瓶防晒霜的SPF值分别为6(可使用2次)和4(可使用1次)。可能的分 配方案为:奶牛1使用第1瓶防晒霜,奶牛2或奶牛3使用第2瓶防晒霜。显然,最 多只有2头奶牛的需求能被满足。 输出格式: * 第1行: 输出1个整数,表示最多有多少头奶牛能享受到日光浴 输出样例 (tanning.out): 2
题解
这道题我用贪心A掉了,相对两个maxSPF一样的牛cow1与cow2,若cow1的minSPF<cow2的minSPF,那么对于一瓶cow1能用的防晒霜,cow2也能用,因为对于答案的贡献都是1,所以给谁都可以,但是若cow2能用的防晒霜cow1未必能用,所以cow1能用应优先给cow1.所以对于所有的牛maxSPF第一关键字升序,minSPF第二关键字降序,然后防晒霜升序,能给就给,贪心就好!考场里还有人用了别的做法->网络流,这个建图是十分显而易见的,复杂度也完全能过。【NOIP模拟赛你们确定要用网络流虐?】
代码
1 /*ZZ*/ 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 int n,l; 6 struct Crow{ 7 int max; 8 int min; 9 }; 10 Crow c[2505]; 11 struct Spf{ 12 int spf; 13 int num; 14 }; 15 Spf s[2505]; 16 inline bool cmp(Crow a,Crow b){ 17 if (a.max<b.max) return true; 18 if (a.max==b.max && a.min>b.min) return true; 19 return false; 20 } 21 int main(){ 22 freopen("tanning.in","r",stdin); 23 freopen("tanning.out","w",stdout); 24 scanf("%d%d",&n,&l); 25 for (int i=1;i<=n;i++) scanf("%d%d",&c[i].min,&c[i].max); 26 for (int i=1;i<=l;i++) scanf("%d%d",&s[i].spf,&s[i].num); 27 sort(c+1,c+n+1,cmp); 28 int index; 29 int Ans=0; 30 for (int i=1;i<=n;i++){ 31 index=-1; 32 for (int j=1;j<=l;j++){ 33 if (s[j].num>0 && c[i].min<=s[j].spf && s[j].spf<=c[i].max){ 34 if (index==-1){ 35 index=j; 36 }else{ 37 if (s[j].spf<s[index].spf) index=j; 38 } 39 } 40 } 41 if (index!=-1){ 42 Ans++; 43 s[index].num--; 44 } 45 } 46 printf("%d\n",Ans); 47 return 0; 48 }
1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<vector> 5 #define inf 19991231 6 using namespace std; 7 int n,l; 8 int s,t; 9 int ans=0; 10 int res; 11 int f[10005]; 12 int a[10005]; 13 int b1[10005],b2[10005]; 14 struct node 15 { 16 int val; 17 int opp; 18 int to; 19 }; 20 vector <node> v[10005]; 21 int d[10050]; 22 void add(int x,int y,int val) 23 { 24 node tmp; 25 tmp.to=y;tmp.val=val;tmp.opp=v[y].size(); 26 v[x].push_back(tmp); 27 tmp.to=x;tmp.val=0;tmp.opp=v[x].size()-1; 28 v[y].push_back(tmp); 29 } 30 bool make_level() 31 { 32 for(int i=1;i<=t;i++)d[i]=-1; 33 d[0]=0; 34 queue <int> q; 35 q.push(0); 36 while(!q.empty()) 37 { 38 int x=q.front(); 39 q.pop(); 40 for(int i=0;i<v[x].size();i++) 41 { 42 if(d[v[x][i].to]==-1 && v[x][i].val>0) 43 { 44 d[v[x][i].to]=d[x]+1; 45 q.push(v[x][i].to); 46 } 47 } 48 } 49 return d[t]!=-1; 50 } 51 int dfs(int x,int cap) 52 { 53 if(x==t)return cap; 54 int r=0; 55 for(int i=0;i<v[x].size();i++) 56 { 57 if(d[v[x][i].to]==d[x]+1 && v[x][i].val>0) 58 { 59 int what=min(v[x][i].val,cap-r); 60 what=dfs(v[x][i].to,what); 61 r+=what; 62 v[x][i].val-=what; 63 v[v[x][i].to][v[x][i].opp].val+=what; 64 } 65 } 66 if(!r)d[x]=-2; 67 return r; 68 } 69 int main() 70 { 71 freopen("tanning.in","r",stdin); 72 freopen("tanning.out","w",stdout); 73 scanf("%d%d",&n,&l); 74 s=0; 75 t=n+l+1; 76 for(int i=1;i<=n;i++) 77 { 78 scanf("%d%d",&b1[i],&b2[i]); 79 add(l+i,t,1); 80 } 81 for(int i=1;i<=l;i++) 82 { 83 int x; 84 scanf("%d%d",&a[i],&x); 85 add(s,i,x); 86 } 87 for(int i=1;i<=l;i++) 88 { 89 for(int j=1;j<=n;j++) 90 { 91 if(a[i]>=b1[j] && a[i]<=b2[j]) 92 { 93 add(i,j+l,1); 94 } 95 } 96 } 97 while(make_level()) 98 { 99 ans+=dfs(s,inf); 100 } 101 printf("%d\n",ans); 102 return 0; 103 }