@xiaoziyao
2021-04-20T12:51:23.000000Z
字数 12693
阅读 2301
考试总结
退役了。游记
难度可能是绿紫紫紫蓝紫,而且有三四道题目卡常,还有一道题目超纲(CCF啪啪打脸)。
题意:张卡牌,正面数字为,反面数字为,一开始所有卡牌正面朝上,可以翻动至多张卡牌,使得朝上的数字极差最小。
分析:
特别简单的签到题,然后CCF就成功脚造数据。(不输入的错误贪心还能过就很离谱)
考虑极差最小等价于值在数轴上对应的区间长度最小,把与的值放在一起排序,在上面双指针求解就好了。
时间复杂度:。
#include<stdio.h>#include<algorithm>#define inf 1000000001using namespace std;const int maxn=1000005;int n,m,ans;int a[maxn],b[maxn],vis[maxn];pair<int,int>c[maxn<<1];inline int get(int x){return x>n? x-n:x;}int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",&a[i]),c[i].first=a[i],c[i].second=i;for(int i=1;i<=n;i++)scanf("%d",&b[i]),c[n+i].first=b[i],c[n+i].second=n+i;sort(c+1,c+1+2*n);int l=1,r=0,cnta=0,cnt=0;while(cnt<n||cnta<n-m){r++;if(c[r].second<=n)cnta++;if(vis[get(c[r].second)]==0)cnt++;vis[get(c[r].second)]++;}ans=c[r].first-c[l].first;while(l<2*n&&r<=2*n){if(c[l].second<=n)cnta--;vis[get(c[l].second)]--;if(vis[get(c[l].second)]==0)cnt--;l++;while(cnt<n||(r<=2*n&&cnta<n-m)){r++;if(c[r].second<=n)cnta++;if(vis[get(c[r].second)]==0)cnt++;vis[get(c[r].second)]++;}if(r>2*n)break;ans=min(ans,c[r].first-c[l].first);}printf("%d\n",ans);return 0;}
可见P7515 [省选联考 2021 A 卷] 矩阵游戏解题报告
题意:
给定大小为的矩阵,求生成任意一个满足条件的满足:
保证,。
分析:
可能是暗中送我退役的一道题,我要是把这个思路想下去做掉这道题,那我剩下的题用头打都能进队啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!
技不如人,肝败吓疯。
首先不考虑,并钦定,不难发现可以生成一组解。
我们考虑通过调整某些位置的权值使得最后的解权值满足限制。
发现对某一行交替后的解仍然满足矩阵的限制;同理,对某一列交替后的解也仍然满足矩阵的限制。
不难发现任意一组解都可以被这样表示出来:
再考虑这一限制,不难发现把序列与序列看做未知量之后可以对其进行差分约束。
但是对于同号的不等式是很难进行差分约束的,考虑定义,
然后再次带入上面的解可以得到一个很优美的形式:
这样就可以进行差分约束了,复杂度:。
Bellman Ford被卡常了(需要特判才能过),迫不得已用了spfa/kk
#include<stdio.h>#include<queue>#include<vector>#define inf 10000000000000000using namespace std;const int maxn=305,maxm=maxn*maxn*2;int T,n,m,e;int a[maxn][maxn],b[maxn][maxn],vis[maxn<<1],tot[maxn<<1];vector<int>v[maxn<<1],w[maxn<<1];long long dis[maxn<<1];queue<int>q;inline void add(int x,int y,int z){v[x].push_back(y),w[x].push_back(z);}inline void limit(int x,int y,int v){add(x,y,v);//v+x-y>=0add(y,x,1000000-v);//v+x-y<=1000000}int spfa(){while(!q.empty())q.pop();for(int i=1;i<=n+m;i++)dis[i]=inf,tot[i]=0,vis[i]=0;dis[1]=0,vis[1]=1,q.push(1);while(!q.empty()){int x=q.front();tot[x]++;if(tot[x]>n+m)return 1;q.pop();for(int i=0;i<v[x].size();i++){int y=v[x][i],z=w[x][i];if(dis[y]>dis[x]+z){dis[y]=dis[x]+z;if(vis[y]==0)vis[y]=1,q.push(y);}}vis[x]=0;}return 0;}inline int calc(int x,int y){return a[x][y]+(int)(((x+y)&1)? (dis[n+y]-dis[x]):(dis[x]-dis[n+y]));}int main(){scanf("%d",&T);while(T--){e=0;scanf("%d%d",&n,&m);for(int i=1;i<n;i++)for(int j=1;j<m;j++)scanf("%d",&b[i][j]);for(int i=2;i<=n;i++)for(int j=2;j<=m;j++)a[i][j]=b[i-1][j-1]-a[i-1][j-1]-a[i-1][j]-a[i][j-1];for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){if((i+j)&1)limit(n+j,i,a[i][j]);else limit(i,n+j,a[i][j]);}if(spfa()){puts("NO");for(int i=1;i<=n+m;i++)v[i].clear(),w[i].clear();continue;}puts("YES");for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)printf("%d%c",calc(i,j),j==m? '\n':' '),a[i][j]=0;for(int i=1;i<=n+m;i++)v[i].clear(),w[i].clear();}return 0;}
可见P7516 [省选联考 2021 A/B 卷] 图函数解题报告
题意:
给定一张个点条边的有向图,定义函数的值为下列操作的返回值:
设,然后按到的顺序枚举点,如果中与能双向到达,那么,并删去结点,最后返回。
定义,并定义为图删除边后的图,求出。
。
分析:
定义为经过的点能否到达,同样设为经过的点能否到达,那么对于图,求的其实就是。
发现如果图改变需要重新处理值,这肯定会超时,因此将中是否能到达改为删除最多的边使得能让其到达的边数(无法到达则为)。
那么如果我们处理出,我们可以求出答案的差分数组,然后前缀和就好了。
考虑类似Floyd的做法,从大到小枚举中转点(这样做可以让循环到时经过的点全部大于等于,便于统计答案),然后对路径进行合并。
具体地,我们对于且可以经过的结点到达(其实就是判断当时的是否非零)的点,枚举终点,那么会存在一条的路径,且不难发现这条路径删除的边数为,用这个权值更新就好了。
同理对于且可以经过的结点到达的点,枚举终点(这里的要小于,否则答案会算重。但其实算重也没关系,因为这一部分的答案已经在之前更新过了),按上面同样的方式更新答案就好了。
时间复杂度:。
#include<stdio.h>const int maxn=1005,maxm=200005;int n,m;int g[maxn][maxn],sum[maxm],ans[maxm];inline int min(int a,int b){return a<b? a:b;}inline int max(int a,int b){return a>b? a:b;}int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)g[i][i]=m+1;for(int i=1;i<=m;i++){int x,y;scanf("%d%d",&x,&y);g[x][y]=i;}for(int k=n;k>=1;k--){for(int i=k;i<=n;i++)sum[min(g[i][k],g[k][i])]++;for(int i=1;i<=k;i++)if(g[i][k])for(int j=1;j<=n;j++)g[i][j]=max(g[i][j],min(g[i][k],g[k][j]));for(int i=k+1;i<=n;i++)if(g[i][k])for(int j=1;j<k;j++)g[i][j]=max(g[i][j],min(g[i][k],g[k][j]));}for(int i=m;i>=1;i--)sum[i]+=sum[i+1];for(int i=1;i<=m+1;i++)printf("%d%c",sum[i],i==m+1? '\n':' ');return 0;}
可见P7518 [省选联考 2021 A/B 卷] 宝石解题报告
题意:
给定一颗个点的带点权的树,以及一个长度为的匹配序列,(点权与序列的值域为),次询问,每次询问到的有向路径与匹配序列匹配的最长前缀的长度。
。
分析:
考场写了一个5k的树分块的做法,然后被卡常卡成就很离谱。
(该部分可略过)
在树上撒个关键点,每个点与其祖先里的关键点距离为,然后发现可以把询问的路径分成个散块和个整块,散块暴力跳(记得把到另一端的路径反向,我因为反向调了一个多小时),整块可以预处理第个关键点跳到祖先关键点这一条链,从位置开始匹配能匹配到哪里,不难发现(我想了十几分钟)可以用并查集维护所有位置同时进行匹配,同样还要反过来做一遍。
具体地,可以把整块对应的链上权值序列当成一个普通的序列,与长度为m的串进行匹配,然后对于每个位置可以维护它在匹配的过程中到了哪一个位置,不难发现把相同的位置在匹配的过程中会不断合并,那么直接上并查集就好了。
然后发现的树上倍增好想好写的一批。
首先进行一次转化,把点权转化为在序列中的位置,这样好做一些。
考虑预处理向上跳第一个点权为的点的位置,这是一个经典问题,在树上用主席树完成就好了,时空复杂度均为。
然后再按照树上倍增的套路预处理一个表示向上跳,从当前点权匹配到的位置,以及表示向上跳,从当前点权反向匹配到的位置。我们首先用主席树帮忙处理出与,然后直接合并就好了。
对于询问,设,考虑让跳到第一个点权为的点开始匹配(需要特判一下跳出这一条链的情况),然后用树上倍增暴力跳数组直到跳出这一条链。
之后,我们发现不能进行类似的操作,因为我们不知道结尾的点权为多少,不难发现答案具有可二分性,直接二分最后的位置,然后按照上面的方法跳就好了。
时间复杂度:。
代码常数似乎很大。
#include<stdio.h>const int maxm=50005,maxn=200005,maxe=maxn<<1,maxk=25;int n,m,c,e,q,tot;int p[maxm],w[maxn],start[maxn],to[maxe],then[maxe],fore[maxn][maxk],dep[maxn],pos[maxm];int lc[maxn*maxk],rc[maxn*maxk],res[maxn*maxk],rt[maxn],inc[maxn][maxk],dec[maxn][maxk];inline void add(int x,int y){then[++e]=start[x],start[x]=e,to[e]=y;}void build(int l,int r,int &now){if(now==0)now=++tot;if(l==r){res[now]=-1;return ;}int mid=(l+r)>>1;build(l,mid,lc[now]),build(mid+1,r,rc[now]);}inline int newnode(int x){tot++,lc[tot]=lc[x],rc[tot]=rc[x],res[tot]=res[x];return tot;}void update(int l,int r,int &now,int pos,int v){now=newnode(now);if(l==r){res[now]=v;return ;}int mid=(l+r)>>1;if(pos<=mid)update(l,mid,lc[now],pos,v);else update(mid+1,r,rc[now],pos,v);}int query(int l,int r,int now,int pos){if(l==r)return res[now];int mid=(l+r)>>1;if(pos<=mid)return query(l,mid,lc[now],pos);return query(mid+1,r,rc[now],pos);}void dfs(int x,int last){dep[x]=dep[last]+1,fore[x][0]=last,rt[x]=rt[last];if(w[x]!=-1)update(1,c,rt[x],w[x],x);inc[x][0]=(w[x]==-1||w[x]==c)? -1:query(1,c,rt[x],w[x]+1);dec[x][0]=(w[x]==-1||w[x]==1)? -1:query(1,c,rt[x],w[x]-1);for(int i=1;i<=20;i++){fore[x][i]=fore[fore[x][i-1]][i-1];inc[x][i]=inc[x][i-1]==-1? -1:inc[inc[x][i-1]][i-1];dec[x][i]=dec[x][i-1]==-1? -1:dec[dec[x][i-1]][i-1];}for(int i=start[x];i;i=then[i]){int y=to[i];if(y==last)continue;dfs(y,x);}}int lca(int a,int b){if(dep[a]<dep[b])a+=b,b=a-b,a-=b;for(int i=20;i>=0;i--)if(dep[fore[a][i]]>=dep[b])a=fore[a][i];if(a==b)return a;for(int i=20;i>=0;i--)if(fore[a][i]!=fore[b][i])a=fore[a][i],b=fore[b][i];return fore[a][0];}int check(int x,int z,int now,int goal){x=query(1,c,rt[x],goal);if(x==-1||dep[x]<dep[z])return 0;for(int i=20;i>=0;i--)if(dec[x][i]!=-1&&dep[dec[x][i]]>dep[z])x=dec[x][i];return w[x]<=now;}int main(){scanf("%d%d%d",&n,&m,&c);for(int i=1;i<=m;i++)pos[i]=-1;for(int i=1;i<=c;i++)scanf("%d",&p[i]),pos[p[i]]=i;for(int i=1;i<=n;i++)scanf("%d",&w[i]),w[i]=pos[w[i]];for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);add(x,y),add(y,x);}build(1,c,rt[0]),dfs(1,0);scanf("%d",&q);while(q--){int x,y,z,res=0;scanf("%d%d",&x,&y),z=lca(x,y);x=query(1,c,rt[x],1);if(x!=-1&&dep[x]>=dep[z]){for(int i=20;i>=0;i--)if(inc[x][i]!=-1&&dep[inc[x][i]]>=dep[z])x=inc[x][i];res=w[x];}int L=res,R=c+1;while(L+1<R){int mid=(L+R)>>1;if(check(y,z,res+1,mid))L=mid;else R=mid;}printf("%d\n",L);}return 0;}
可见P7519 [省选联考 2021 A/B 卷] 滚榜解题报告
题意:给定支队伍,每支队伍有权值。给定,将分成若干个数之和,按不降顺序分配给个队伍,已知每次分配都会让分配给的队伍权值变为最大(权值相同比较编号大小,小的优先),求分配方案数。()
很简单的状压dp+费用提前,但考场降智了,没想到。
设为当前分配完的队伍集合为,上一个分配的队伍为,一共消耗的权值大小为的方案数,可是由于分配权值的顺序必须不降,所以还得带上另一个参数来表示上一次分配的权值,可是这样就是的了,比阶乘还劣。
考虑表示出或消除这个参数,根据上面的定义表示出这个参数比较难,因此我们想一想如何将这个参数的费用消除掉。
我们先想一想当我们确定了分配顺序应该怎么判断:我们按照顺序给每个队伍分配最少的权值,最后如果分配失败就说明方案失败,否则可以直接把所有剩下权值分配给最后一个队伍。
我们考虑剩下的权值不分配给最后一个队伍的情况,这时可以对于一个队伍后缀增加一个不递减的权值序列,而对这个序列差分后可以知道这个权值序列可以分成若干次给某个后缀加上相同的权值。
这提醒了我们使用费用提前,转移到(其中集合仅含)时,不难发现需要给队伍增加权值(设一开始权值最大的队伍为),而根据上面的思考可以知道这个权值在以后每个队伍都需要进行分配,那么我们直接将权值乘,提前计算费用即可。
同理,对转移到,我们对于当前队伍需要增加的权值,我们将权值乘就可以消除后续的贡献。
时间复杂度:,用来枚举可以卡卡常。
#include<stdio.h>#define lowbit(x) x&-xconst int maxn=13,maxm=505,maxk=(1<<maxn);int n,m,k,maxx,maxp;int a[maxn+1],tot[maxk],p[maxk];long long ans;long long f[maxk][maxn+1][maxm];inline int max(int a ,int b){return a>b? a:b;}int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%d",&a[i]),p[1<<(i-1)]=i;if(a[i]>maxx)maxx=a[i],maxp=i;}k=(1<<n)-1,tot[0]=0;for(int i=1;i<=k;i++)tot[i]=tot[i>>1]+(i&1);for(int i=1;i<=n;i++){int cost=n*(maxx-a[i]+(maxp<i));if(cost<=m)f[1<<(i-1)][i][cost]=1;}for(int i=1;i<k;i++)for(int j=i;j;j-=lowbit(j))for(int s=0;s<=m;s++){int v=lowbit(j),now=p[v];if(f[i][now][s]==0)continue;for(int t=1;t<=n;t++)if(((i>>(t-1))&1)==0){int cost=s+(n-tot[i])*max(a[now]-a[t]+(now<t),0);if(cost<=m)f[i|(1<<(t-1))][t][cost]+=f[i][now][s];}}for(int i=0;i<=n;i++)for(int j=1;j<=m;j++)ans+=f[k][i][j];printf("%lld\n",ans);return 0;}
可见P7520 [省选联考 2021 A 卷] 支配解题报告
题意:
定义一个点支配另一个点当且仅当结点到达结点的每一条路径都要经过,且点的受支配集为所有支配的形成的集合。
给定一个个点条边的图,进行次相互独立的询问,每次询问加入边后有多少个点的受支配集有变化。
。
分析:
似乎是支配树裸题,但我不会支配树/kk。
当我们不知道支配树时,应该怎么想到支配树呢?
考虑到结点的所有路径一定是不断“扩张”然后不断“收束”到一个点上,然后继续“扩张”与“收束”的一个过程,而每次“收束”到的点都是的支配点。
不难发现除了结点外任意结点都有且仅有一个极大支配点,也就是说,那么如果我们将与连一条边的话,根据支配的定义一定不会形成环,那么就会形成一个个结点,条边的无向无环联通图——支配树。
如何建出支配树呢?这里介绍一种的简单做法。
考虑设表示删除结点后,结点是否能到达结点,那么我们只需要枚举,然后每次就可以的求出这个数组了。
又由于与支配等价,因此我们可以求出所有结点的受支配集。
那么我们对于每个点枚举它受支配集中每一个点,按照极大支配点的定义进行判断就好了。
建出支配树,又怎么求解呢?(设加入的边为)
对于每一个点,不难发现它的受支配集会改变当且仅当的受支配集改变或者不再支配。(由极大支配点的定义可得)
那么我们从上到下遍历支配树(不要直接遍历,最好用序或者序,本文使用序,也就是所有点按照支配集大小进行排序的结果),问题转化为判断一个点的极大支配点是否还支配。
很容易发现支配树的支配关系是等价于原图的(这也是CCF设置图是一个树这个部分分的原因),那么我们只需要判断删除的父亲从是否能到达,而是否能到达就好了。
那么我们再预处理一个表示删除的父亲后是否能到达就做完这道题了。
时间复杂度:。
代码被卡常了,交换了与定义中的与才能过。
#include<stdio.h>#include<vector>#include<algorithm>using namespace std;const int maxn=3005;int n,m,q,e;int bfn[maxn],fa[maxn],t[maxn],ok[maxn];int delp[maxn][maxn],delf[maxn][maxn];//delp[y][x]: del x 1 to y ; delf[y][x]: del fa[x] 1 to yvector<int>g[maxn],rg[maxn],d[maxn];inline int cmp(int x,int y){return d[x].size()<d[y].size();}void getdelp(int x,int p){if(x==p)return ;delp[x][p]=1;for(int i=0;i<g[x].size();i++){int y=g[x][i];if(delp[y][p]==0)getdelp(y,p);}}void getdelf(int x,int p){if(x==fa[p])return ;delf[x][p]=1;for(int i=0;i<rg[x].size();i++){int y=rg[x][i];if(delf[y][p]==0)getdelf(y,p);}}void read(int &x){x=0;char c=getchar();for(;c<'0'||c>'9';c=getchar());for(;c>='0'&&c<='9';c=getchar())x=x*10+c-48;}int main(){scanf("%d%d%d",&n,&m,&q);for(int i=1;i<=m;i++){int x,y;read(x),read(y);g[x].push_back(y),rg[y].push_back(x);}for(int i=1;i<=n;i++){getdelp(1,i);for(int j=1;j<=n;j++)if(delp[j][i]==0)d[j].push_back(i);}for(int i=2;i<=n;i++)for(int j=0;j<d[i].size();j++)if(d[d[i][j]].size()==d[i].size()-1){fa[i]=d[i][j];break;}for(int i=1;i<=n;i++)bfn[i]=i;sort(bfn+1,bfn+1+n,cmp);for(int i=2;i<=n;i++)getdelf(i,i);for(int i=1;i<=q;i++){int x,y,res=0;read(x),read(y);for(int j=1;j<=n;j++)if(fa[j]!=1&&fa[j]!=x&&delp[x][fa[j]]&&delf[y][j])ok[j]=1;for(int j=2;j<=n;j++){ok[bfn[j]]|=ok[fa[bfn[j]]];res+=ok[bfn[j]];}for(int j=1;j<=n;j++)ok[j]=0;printf("%d\n",res);}return 0;}