2019-8-11 考试总结
A. 入阵曲
直接二维前缀和复杂度$O(n^4)$可得$60$。
另外的性质分数可得$15$,暴力$75$。
正解是$O(n^3)$。
设右下角格子坐标为$(i,j)$,左上角格子为$(p,k)$。
那么满足条件的必须是$(sum[i][j]-sum[i][k-1]-sum[p-1][j]+sum[p-1][k-1])\% k=0$,
就是$sum[i][j]-sum[i][k-1]=sum[p-1][j]-sum[p-1][k-1]$,
然后这个可以用一个桶存余数为$i$的数的个数$S[i]$,
然后先枚举列,再枚举行,把每一行的$sum[i][j]-sum[i][k-1]$存下来。
然后就$O(1)$查找,复杂度$O(n^3)$。
丑陋的代码:
#include<iostream> #include<cstring> #include<cstdio> #define Reg register #define Maxn 450 using namespace std; int n,m,k,W[Maxn][Maxn],sum[Maxn][Maxn],g[1000050]; long long ans=0; int main() { scanf("%d%d%d",&n,&m,&k); for(Reg int i=1;i<=n;++i) { for(Reg int j=1;j<=m;++j) { scanf("%d",&W[i][j]); sum[i][j]=(sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+W[i][j]+k)%k; } } for(Reg int j=1;j<=m;++j) { for(Reg int p=j;p>=1;--p) { for(Reg int i=1,x;i<=n;++i) { x=(sum[i][j]-sum[i][p-1]+k)%k; ans+=g[x]; if(x==0) ++ans; ++g[x]; } for(Reg int i=1,x;i<=n;++i) { x=(sum[i][j]-sum[i][p-1]+k)%k; --g[x]; } } } printf("%lld",ans); return 0; }
B. 将军令
好吧,又是贪心。
没想到要贪心,想拿$k=2$之前的分,然后$dp$状态都没定对。
最后就拿了$40$分。
每次贪心找最深的没有被守的节点的$k$次祖先,然后守他的$k$次祖先。
这样贪心为什么对的,
因为如果对于最深的节点,如果不守他的$k$次祖先,那么肯定会守深度更大的一个节点,
而深度更大的节点肯定不比它更优。
这可以感性地理解一下。
不知道能不能严谨地证明。
丑陋的代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<vector> #define Reg register #define Maxn 100050 #define INF 0x7ffffffff #define _min(x,y) ((x)<(y)?(x):(y)) using namespace std; bool vis[Maxn]; int n,k,t,ans,tot,fir[Maxn],dep[Maxn],num[Maxn],fat[Maxn],vap[Maxn]; struct Tu {int st,ed,next;} lian[Maxn*2]; vector<vector<int> > son(Maxn); void add(int x,int y) { lian[++tot].st=x; lian[tot].ed=y; lian[tot].next=fir[x]; fir[x]=tot; return; } bool comp(int x,int y) {return dep[x]>dep[y];} void dfs(int x) { vis[x]=1; for(Reg int i=fir[x];i;i=lian[i].next) { if(vis[lian[i].ed]) continue; dep[lian[i].ed]=dep[x]+1; dfs(lian[i].ed); fat[lian[i].ed]=x; son[x].push_back(lian[i].ed); } return; } void update(int x,int len,int k) { vis[x]=1; if(len==0||vap[x]>len) return; vap[x]=len; for(Reg int i=0,p;i<son[x].size();++i) { p=son[x][i]; if(p!=k) update(p,len-1,0); } update(fat[x],len-1,x); return; } int main() { // freopen("text.in","r",stdin); scanf("%d%d%d",&n,&k,&t); for(Reg int i=1,x,y;i<=n-1;++i) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } dep[1]=1; dfs(1); for(Reg int i=1;i<=n;++i) num[i]=i; sort(num+1,num+n+1,comp); memset(vis,0,sizeof(vis)); for(Reg int i=1;i<=n;++i) { if(!vis[num[i]]) { int now=num[i]; for(Reg int j=1;j<=k;++j) { if(fat[now]) now=fat[now]; else break; } ++ans; update(now,k,0); } } printf("%d",ans); return 0; }
C. 星空
正解:
把亮的灯泡看作$1$,不亮的灯泡看作$0$,
每次修改会改一个连续的长度为$L$的区间,把区间中的灯泡异或$1$,
把区间差分,设$b[i]=a[i]\wedge a[i-1]$,并且让$a[0]=1$,
为了方便,把$b[i]\wedge 1$。
那么现在要做的就是修改左右端点,让整个$b$序列变成$1$。
修改区间时要修改的是$i$和$i+len$。
每次修改肯定要修改有$0$的地方,否则肯定不优。
那么如果修改的两个端点一个是$0$一个是$1$,那么就相当于把$1$变成$0$,把$0$变成$1$。
如果两个都是$0$,那么这两个$0$都会变成$1$,
下面是玄学操作:
把$0->1$看作点的移动,第二种情况也看作点的移动。
相当于$0$可以移动到$1$的位置,而两个$0$相碰会消失。。。
接下来要做的就是把所有的$0$都相碰。
转化问题:移动所有的$0$,让所有的$0$两两相碰。
接下来可以$bfs$,求出每个$0$到其他$0$的最短距离。
再转化问题:有一堆操作,每次操作可以让两个$0$消失,代价为$dis[i][j]$,问让所有的$0$消失的最小代价。
由于$k$非常的小,考虑状压。
方程很好写。
丑陋的代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<queue> #define Maxn 400050 #define Reg register #define INF 0x7fffff #define _min(x,y) ((x)<(y)?(x):(y)) using namespace std; int n,k,m,cnt,tot,ans,A[Maxn],lss[Maxn],len[Maxn],num[Maxn],dis[250][250]; int dp[1<<17],bin[1<<18]; bool vis[250][Maxn]; int q[Maxn],p[Maxn]; void bfs(int x) { queue<int> q,p; q.push(x); p.push(0); while(!q.empty()) { int k=q.front(); q.pop(); int l=p.front(); p.pop(); if(num[k]&&k!=x) dis[num[x]][num[k]]=l; vis[num[x]][k]=1; for(Reg int i=1;i<=m;++i) { if(k+len[i]<=n+1&&!vis[num[x]][k+len[i]]) { vis[num[x]][k+len[i]]=1; q.push(k+len[i]); p.push(l+1); } if(k-len[i]>=1&&!vis[num[x]][k-len[i]]) { vis[num[x]][k-len[i]]=1; q.push(k-len[i]); p.push(l+1); } } } return; } int main() { // freopen("text.in","r",stdin); // freopen("text1.out","w",stdout); scanf("%d%d%d",&n,&k,&m); A[0]=A[n+1]=1; memset(dis,0xf,sizeof(dis)); for(Reg int i=1;i<=n;++i) A[i]=1; for(Reg int i=1,x;i<=k;++i) {scanf("%d",&x); A[x]=0;} for(Reg int i=0;i<=n;++i) lss[i]=A[i]^A[i+1]^1; for(Reg int i=1;i<=n+1;++i) A[i]=lss[i-1]; for(Reg int i=1;i<=m;++i) scanf("%d",&len[i]); for(Reg int i=1;i<=n+1;++i) if(!A[i]) num[i]=++cnt; for(Reg int i=1;i<=n+1;++i) if(num[i]) bfs(i); for(Reg int i=0;i<=cnt;++i) bin[1<<i]=i; memset(dp,0xf,sizeof(dp)); dp[(1<<cnt)-1]=0; ans=INF; for(Reg int i=1;i<=cnt;++i) { for(Reg int k=0;k<(1<<cnt);++k) { if((k&(1<<(i-1)))==0) continue; for(Reg int l=k;l;l-=(l&-l)) { int j=bin[(l&-l)]+1; if((l&-l)==0||j==i||j==0) continue; dp[k^(1<<(j-1))^(1<<(i-1))]=_min(dp[k^(1<<(j-1))^(1<<(i-1))],dp[k]+_min(dis[j][i],dis[i][j])); } } } printf("%d",dp[0]); return 0; }
总结:
翻看之前的考试总结,好像真的是流水帐。。。
每次考完都感觉很。。没有好好考虑自己的问题。
前半小时读完$T1$的题,然后码了暴力,感觉基本上能拿到$75$分的好成绩。
滚去看$T2$,想到两个思路,一个二分,一个树形$dp$,
二分很快否定,树形$dp$想拿到$k=0,k=1,k=2$的分数。
好像打了不到$2$个小时,手摸样例基本都对。
然后胆战心惊地跳过了这道题,以为自己能拿到$75+60+$的成绩。
$T3$没想拿多少分,因为时间不是很多,之后还要再想$T1$正解。
最近几套题部分分给的很多。
最后$65+40+12=117$。
考场上想不到非常正确的思路,$T2$的$dp$状态都定义错了。。。
最近考试的贪心出现很频繁,如果想到整个题很简单,否则非常麻烦。
$T1$好好想应该能够想出来,不能很快放弃。
好好反思。