2017/8/2 考试吐槽
2017 8 2 得分:70
首先扯点题外话:之前那么多天考试从来不公开吐槽,主要是因为每天都有我们的学长出的题,涉及到了他们的知识产权,我不方便发出来。但是今天!没有!全是原题!因此我要向全世界吐槽一发!让全世界感受到我的蒟蒻!(滑(fa)稽(gi))好了不废话了,现在进入正题:吐槽+题解……
一句话:代码长度与正确性一定成反比!
A、路面修整
吐槽:果然是我太弱了,这个东西都想不出来……
链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1592
题意:求把一个序列修改成非上升或非下降序列最小花费。
$SBDP$,但我并不会表示状态……┑( ̄Д  ̄)┍考试XJBD拿了40分细软跑就滚粗了……
正解也没啥说的,就是SBDP,下面仅以求不下降序列花费为例。设$f[i][j]$为走到$i$最小花费为$j$(由于数据太大,高度需要离散化),那么,$f[i][j]=min{f[i-1][k]+abs(Hash[j]-Hash[a[k]])|1<=k<=j}$(Hash是离散化数组的名字)。但是我们会发现这么转移是$O(n^3)$,需要我们做出显而易见然而你就是没想到的优化:开一个数组存上一轮到$j$为止的最小花费,然后乱搞就可以了……
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cstdlib> 6 using namespace std; 7 const int maxn=2005; 8 int n,a[maxn],Hash[maxn],f1[maxn][maxn],cnt,prefix[maxn],f2[maxn][maxn],suffix[maxn]; 9 void Lisan() 10 { 11 sort(Hash+1,Hash+n+1);cnt=unique(Hash+1,Hash+n+1)-Hash-1; 12 for(int i=1;i<=n;i++)a[i]=lower_bound(Hash+1,Hash+cnt+1,a[i])-Hash; 13 } 14 int haha() 15 { 16 //freopen("grading.in","r",stdin); 17 //freopen("grading.out","w",stdout); 18 scanf("%d",&n); 19 for(int i=1;i<=n;i++)scanf("%d",&a[i]),Hash[i]=a[i]; 20 Lisan(); 21 memset(f1,0x7f,sizeof(f1)); 22 memset(f2,0x7f,sizeof(f2)); 23 for(int i=1;i<=cnt;i++)f1[0][i]=f2[0][i]=0; 24 for(int i=1;i<=n;i++) 25 { 26 for(int j=1;j<=cnt;j++) 27 { 28 f1[i][j]=prefix[j]+abs(Hash[j]-Hash[a[i]]),f2[i][j]=suffix[j]+abs(Hash[j]-Hash[a[i]]); 29 } 30 for(int j=1;j<=cnt;j++) 31 { 32 prefix[j]=f1[i][j]; 33 if(j>1)prefix[j]=min(prefix[j],prefix[j-1]); 34 } 35 for(int j=cnt;j>=1;j--) 36 { 37 suffix[j]=f2[i][j]; 38 if(j<cnt)suffix[j]=min(suffix[j],suffix[j+1]); 39 } 40 } 41 int ans1=2147483647,ans2=2147483647; 42 for(int i=1;i<=cnt;i++)ans1=min(ans1,f1[n][i]),ans2=min(ans2,f2[n][i]); 43 printf("%d\n",min(ans1,ans2)); 44 } 45 int sb=haha(); 46 int main(){;}
B、翻格子游戏
吐槽:这不是一个裸的解异或方程组吗……竟然暴力枚举……这种事情让我也很无奈呢……
链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1647
题意:翻动一次方块会使该方块以及周围相邻的四个方块上下发生变化,目标是使所有方块反面向上。请输出翻动次数最小的方案中字典序最小的一种。
这题我考试时想的真的是解异或方程组,结果打出来发现自己并不会写= =……正解出来炸裂了我的神经……
正解竟然是枚举!惊不惊喜!意不意外!开不开心!我们可以看到,$N<=15$,那么我们就可以欢脱的枚举第一排的情况(这个当然可以在$O(2^N)$时间内完成),对于每一个状态,我们在每一行可以再用$O(N)$的时间计算出下一行应翻动的次数和下一行的即时状态。我们可以知道,每一个格子的情况是由总共5个格子决定的,当我们递推到下一行的状态时,前四个已经执行完毕,只剩最后一个还没有决定。于是我们就可以执行令人窒息的操作:上一行翻完后的状态是什么,这一行就怎么翻。$M$行全部检查完成后,检查最后一行,因为最后一行无法再翻。如果合格,计算翻动次数,合适就复制矩阵,然后,就没有然后了……
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int maxn=20; 7 int n,map[maxn][maxn],effort[maxn][maxn],result[maxn][maxn],tmp[maxn][maxn],m,ans=0x7fffffff; 8 void solve(int val) 9 { 10 memset(tmp,0,sizeof(tmp)); 11 int l=val; 12 for(int i=0;val;val>>=1,i++)tmp[1][m-i]=val&1; 13 for(int i=1;i<=m;i++) 14 { 15 result[1][i]=map[1][i]^tmp[1][i]; 16 if(i>1)result[1][i]^=tmp[1][i-1]; 17 if(i<m)result[1][i]^=tmp[1][i+1]; 18 } 19 for(int i=2;i<=n;i++) 20 { 21 for(int j=1;j<=m;j++) 22 if(result[i-1][j]!=0) 23 { 24 tmp[i][j]=1; 25 result[i-1][j]^=1; 26 } 27 for(int j=1;j<=m;j++) 28 { 29 result[i][j]=map[i][j]^tmp[i][j]; 30 result[i][j]^=tmp[i-1][j]; 31 if(j>1)result[i][j]^=tmp[i][j-1]; 32 if(j<m)result[i][j]^=tmp[i][j+1]; 33 } 34 } 35 for(int i=1;i<=m;i++) 36 if(result[n][i])return; 37 int temp=0; 38 for(int i=1;i<=n;i++) 39 for(int j=1;j<=m;j++)temp+=tmp[i][j]; 40 if(temp<ans) 41 { 42 ans=temp; 43 memcpy(effort,tmp,sizeof(tmp)); 44 } 45 } 46 void print() 47 { 48 for(int i=1;i<=n;i++) 49 { 50 for(int j=1;j<=m;j++)printf("%d ",effort[i][j]); 51 puts(""); 52 } 53 } 54 int haha() 55 { 56 //freopen("fliptile.in","r",stdin); 57 //freopen("fliptile.out","w",stdout); 58 scanf("%d%d",&n,&m); 59 for(int i=1;i<=n;i++) 60 for(int j=1;j<=m;j++)scanf("%d",&map[i][j]); 61 int att=(1<<m),tmp; 62 for(tmp=0;tmp<att;tmp++)solve(tmp); 63 if(ans<0x7fffffff)print(); 64 else puts("IMPOSSIBLE"); 65 } 66 int sb=haha(); 67 int main(){;}
C、枪战
吐槽:dalao们丧心病狂的码量……我怀疑我是不是走错片场了?
链接:http://cogs.pro/cogs/problem/problem.php?pid=2487
(咳,不要在意这个题面,cogs上膜法师一大片大家又不是不知道,这不是重点……)
题意:每个人最多枪毙一个人(可以自杀),求最少、最多死了多少人。
考试时只想到了Tarjan……并没有考虑全面……于是……10分全场最高分……GG……
首先我们围观一下大猩猩的挑战纪录
咳,扯远了,接下来我们回来讨论人类的解题方法。首先考虑最多会死多少人,在全图中考虑每个连通分量,大概会有如下三种情况:
1、只有一个点,即自尽——全灭
2、好几个点连环套——只活一个
3、链连着环——只有入度为0的人才能活下来。
分别考虑即可。
接下来我们回来考虑人数最少的情况。首先我们找到所有入度为0的人,将他们压进队列,随后,对于每个人,取出来,跟他们连着的人全灭。然后,这些人死后我们来看这些人瞄着的人是不是没人瞄了,如果是,也进队列。这样操作完成后,剩下的就都是环,死的人一定是环大小的一半。问题就解决了。
有一点需要注意:这道题极限数据1000000,你连点都搜不完,因此需要手动开栈。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int maxn=1000005; 7 int belong[maxn],dfn[maxn],low[maxn],scnt,cnt,size[maxn],n,minn,maxx,nxt[maxn]; 8 #include<stack> 9 stack<int>s; 10 void dfs(int u) 11 { 12 low[u]=dfn[u]=++cnt; 13 s.push(u); 14 int v=nxt[u]; 15 if(!dfn[v]) 16 { 17 dfs(v); 18 low[u]=min(low[u],low[v]); 19 } 20 else if(!belong[v]) low[u]=min(low[u],dfn[v]); 21 if(low[u]==dfn[u]) 22 { 23 int k;scnt++; 24 do 25 { 26 k=s.top();s.pop(); 27 belong[k]=scnt;size[scnt]++; 28 }while(k!=u); 29 } 30 } 31 int entrance_degree[maxn],die[maxn],vis[maxn]; 32 #include<queue> 33 queue<int>q; 34 int main() 35 { 36 freopen("maf.in","r",stdin); 37 freopen("maf.out","w",stdout); 38 int __size__= 128 << 20; 39 char *__p__ = (char*)malloc(__size__) + __size__; 40 __asm__("movl %0, %%esp\n" :: "r"(__p__)); 41 scanf("%d",&n); 42 for(int i=1;i<=n;i++) 43 { 44 scanf("%d",&nxt[i]); 45 die[nxt[i]]=1; 46 entrance_degree[nxt[i]]++; 47 } 48 for(int i=1;i<=n;i++) 49 if(!die[i]) 50 { 51 q.push(i); 52 vis[i]=1; 53 } 54 while(!q.empty()) 55 { 56 int u=q.front();q.pop();vis[u]=1; 57 if(!vis[nxt[u]]) 58 { 59 vis[nxt[u]]=die[nxt[u]]=1;minn++; 60 if(!(--entrance_degree[nxt[nxt[u]]]))q.push(nxt[nxt[u]]); 61 } 62 } 63 for(int i=1;i<=n;i++) 64 if(!vis[i]) 65 { 66 int tmp=0,now=i; 67 while(!vis[now]) 68 { 69 vis[now]=1; 70 tmp++; 71 now=nxt[now]; 72 } 73 minn+=(tmp+1)>>1; 74 } 75 for(int i=1;i<=n;i++) 76 if(!dfn[i])dfs(i); 77 memset(entrance_degree,0,sizeof(entrance_degree)); 78 for(int i=1;i<=n;i++) 79 if(belong[i]!=belong[nxt[i]]||i==nxt[i])entrance_degree[belong[nxt[i]]]++; 80 for(int i=1;i<=scnt;i++) 81 if(!entrance_degree[i])maxx++; 82 printf("%d %d\n",minn,n-maxx); 83 }
成绩嘛……我就不贴了……你们都看到了不是吗?(逃)
(去颓下一篇博文去了)