noip多校21
Posted WindZR
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了noip多校21相关的知识,希望对你有一定的参考价值。
好几天没写博客了,一直在改题,挺累的。今天的题实在有些不可改,最后剩下一些时间来写一写博客,当做总结,也当做放松一下。
这次考试,挂分直接挂没了,问题出现在两个方面:
1.没有计算时间复杂度,代码实现常数过大
2.没有仔细读题,没有清楚题目让干什么。
其实,这两个问题都是可以避免的,对于计算时空复杂度,应该是一个习惯,不能凭感觉。
对于审题这件事,还是要细心,尤其是对于题目描述比较简单的情况下,也可以多读几遍。如果始终过不了样例也可以再读一遍题。
剩下的就是考试策略问题,我觉得我的策略需要改进,之前都是看到那种纯数学题,期望题就直接跳过,现在觉得这样的做法是不可行的,因为最近几天的考试题都只是披着一个期望的皮,实际上和概率那些东西没什么关系,只要想一想应该就可以想出来的。
然后就是对于考试心态,如果整套题题目都是偏难,首先要稳住心态,尽可能拿到自己可以拿到的分,以不挂分为主。
T1 按位或
咕了
T2 最短路径
思路:如果我们规定从起点出发后还要回到起点,那么我么走过的路径长度就是我经过的所有边的距离2-起点到一个点的距离。
那么假设这\\(k\\)个点已经确定,路径长度就是经过的所有边的距离2-最长的链的距离。
考虑分别计算。
对于前一部分,我们可以在\\(dfs\\)的过程中求出。一条边有贡献当且仅当它两端都右可以当做黑点的点。那么这条边被经过的方案数就是\\(C_{m,k}-C_{sum,k}-C{m-sum,k}\\),其中,\\(sum\\)表示这条边下面的端点为根的子树中的可以作为黑点的数量。
对于后一部分,我们枚举两个点,将这两个点之间的距离作为一条链,然后再枚举这条链可以对多少点产生贡献,用\\(ans-C(k-2,cnt)\\times len\\)即可。
代码如下:
AC_code
#include<bits/stdc++.h>
#define int long long
#define re register int
#define ii inline int
#define iv inline void
#define next netete
#define head haeaead
using namespace std;
const int N=2010;
const int mo=998244353;
int n,m,k,tot,ans;
bool vis[N];
int cun[N],jc[N],inv[N];
int to[N<<1],next[N<<1],head[N];
int deep[N],size[N],fa[N],top[N],son[N];
int dis[N][N],sum[N];
ii read()
{
int x=0; char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'-\') f=0;
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-x);
}
ii ksm(int d,int z)
{
int out=1;
while(z)
{
if(z&1) out=out*d%mo;
z>>=1;
d=d*d%mo;
}
return out;
}
ii C(int n,int m)
{
if(m>n) return 0;
if(m==0 or n==0) return 1;
return jc[n]*inv[n-m]%mo*inv[m]%mo;
}
iv add(int x,int y)
{
to[++tot]=y;
next[tot]=head[x];
head[x]=tot;
}
iv dfs(int st,int f)
{
fa[st]=f;
size[st]=1;
deep[st]=deep[f]+1;
if(vis[st]) sum[st]=1;
for(re i=head[st];i;i=next[i])
{
int p=to[i];
if(p==f) continue;
dfs(p,st);
if(sum[p] and m-sum[p]) ans=(ans+C(m,k)-C(sum[p],k)-C(m-sum[p],k)+mo+mo)%mo;
size[st]+=size[p];
sum[st]+=sum[p];
son[st]=(size[son[st]]>size[p])?son[st]:p;
}
}
iv dfs2(int st,int t)
{
top[st]=t;
if(!son[st]) return ;
dfs2(son[st],t);
for(re i=head[st];i;i=next[i])
{
int p=to[i];
if(p==son[st] or p==fa[st]) continue;
dfs2(p,p);
}
}
ii get_lca(int x,int y)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(deep[fx]<deep[fy]) swap(x,y),swap(fx,fy);
x=fa[fx],fx=top[x];
}
return deep[x]<deep[y]?x:y;
}
signed main()
{
freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
n=read(),m=read(),k=read();
int x,y;
jc[0]=1;
for(re i=1;i<=305;i++) jc[i]=jc[i-1]*i%mo;
inv[305]=ksm(jc[305],mo-2);
for(re i=304;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mo;
for(re i=1;i<=m;i++) cun[i]=read(),vis[cun[i]]=1;
for(re i=1;i<n;i++)
{
x=read(),y=read();
add(x,y),add(y,x);
}
dfs(1,0);dfs2(1,1);
for(re i=1;i<=m;i++)
{
for(re j=i+1;j<=m;j++)
{
int lca=get_lca(cun[i],cun[j]);
dis[cun[i]][cun[j]]=dis[cun[j]][cun[i]]=deep[cun[i]]+deep[cun[j]]-2*deep[lca];
}
}
ans=ans*2ll%mo;
for(re i=1;i<=m;i++)
{
for(re j=i+1;j<=m;j++)
{
int cnt=0;
for(re p=1;p<=m;p++)
{
if(p==i or p==j) continue;
if(dis[cun[p]][cun[i]]<dis[cun[i]][cun[j]] and dis[cun[p]][cun[j]]<dis[cun[i]][cun[j]]) ++cnt;
else if((dis[cun[p]][cun[i]]==dis[cun[i]][cun[j]] and p<j) and (dis[cun[p]][cun[j]]==dis[cun[i]][cun[j]] and p<i)) ++cnt;
else if(dis[cun[p]][cun[i]]==dis[cun[i]][cun[j]] and p<j and dis[cun[p]][cun[j]]<dis[cun[i]][cun[j]]) ++cnt;
else if(dis[cun[p]][cun[j]]==dis[cun[i]][cun[j]] and p<i and dis[cun[p]][cun[i]]<dis[cun[i]][cun[j]]) ++cnt;
}
ans=(ans-C(cnt,k-2)*dis[cun[i]][cun[j]]%mo+mo)%mo;
}
}
printf("%lld\\n",ans*ksm(C(m,k),mo-2)%mo);
return 0;
}
T3 仙人掌
咕了
T4 对弈
咕了
noip多校模拟23
考试过程:开考先看题,觉得按顺序开题比较合适。首先是T1,刚开始没什么思路,而且比较困,神游了半个小时。然后突然有了思路,想到了回文串必定会有一个对称中心,那么我们可以从这个中心出发向两边扩展,设当前矩形的左上角和右下角分别是\\(x1,y1,x2,y2\\),那么可以扩展到
\\(x1-1,y1,x2+1,y2\\)
\\(x1-1,y1,x2,y2+1\\)
\\(x1,y1-1,x2+1,y2\\)
\\(x1,y1-1,x2,y2+1\\)
四个方向的矩形,直接转移即可,这样的复杂度是\\(o(n^4)\\),可以获得\\(50pts\\),然后我想了一会,觉得应该可以用类似与步数的方式优化到\\(o(n^3)\\),但是没什么具体的思路,就先打后面的题。
T2,阅读程序题,指针满天飞,属实看不懂,就弃掉了。
T3,想了一会想到一个\\(dp\\)思路,可以拿到\\(44pts\\),但是考场上打挂了,只拿了\\(12pts\\),错了好多小点,所以以后一定要测试点分治。
T4,没什么时间想了,就按照题意模拟,拿了\\(42pts\\)
总结:1.一定要打测试点分治
2.在看不懂题的情况下不要放弃,可以先看特殊性质,拿到白送的分。
3.如果有优化的思路,就要留出时间思考。
T1 回文
思路:暴力思路上面已经说过了,现在考虑如何优化。从对称中心出发,那么到达\\((1,1)\\)和\\((n,m)\\)的步数是一样的,所以一定满足这样的柿子\\(x1=n-x2+1,y1=m-y2+1\\),那么一定满足\\(x1+y1+x2+y2==n+m+2\\),那么如果我们知道了\\(x1,y1,x2\\),那么\\(y2\\),可以通过计算得知,这样的复杂度就降到了\\(o(n^3)\\)。
所以我们设\\(f_{i,j,k}\\)表示枚举的矩形的左上角为\\((i,j)\\),右下角的横坐标为\\(k\\)的方案数。转移是显然的。
最后考虑如何统计答案,我们找到所有\\(x+y-1==((n+m-2)/2)+1\\)的点,那么只需要\\(ans+=f_{x,y,x+1}+f_{x,y,x}\\)即可。
代码如下:
AC_code
#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int mo=993244853;
const int N=505;
int f[N][N][N];
char s[510][510];
int n,m,ans;
ii read()
{
int x=0; char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'-\') f=0;
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-x);
}
iv add(int &x,int y)
{
x=x+y;
if(x>=mo) x-=mo;
}
int main()
{
freopen("palin.in","r",stdin);
freopen("palin.out","w",stdout);
n=read(),m=read();
for(re i=1;i<=n;i++)
{
scanf("%s",s[i]+1);
}
if(s[1][1]!=s[n][m]) {printf("0\\n");return 0;}
f[1][1][n]=1;
for(re i=1;i<=n;i++)
{
for(re j=1;j<=m;j++)
{
for(re k=n;k>=i;k--)
{
int p=n+m-i-j-k+2;
if(i==k and j==p) continue;
if(p<j) continue;
if((k-1>=i and j+1<=p) and s[i][j+1]==s[k-1][p]) add(f[i][j+1][k-1],f[i][j][k]);
if((p-1>=j+1) and s[i][j+1]==s[k][p-1]) add(f[i][j+1][k],f[i][j][k]);
if((k-1>=i+1) and s[i+1][j]==s[k-1][p]) add(f[i+1][j][k-1],f[i][j][k]);
if((i+1<=k and p-1>=j) and s[i+1][j]==s[k][p-1]) add(f[i+1][j][k],f[i][j][k]);
}
}
}
int len=(n+m-2)>>1;
for(re i=1;i<=n;i++)
{
for(re j=1;j<=m;j++)
{
if((i+j-1)==len+1)
{
add(ans,f[i][j][i+1]);
add(ans,f[i][j][i]);
}
}
}
printf("%d\\n",ans);
return 0;
}
T2 快速排序
思路:经过计算机带师ICEY
的讲解,终于看懂了题意。
首先,如果所有被排序数都非 nan,那么必然是按升序排列。
否则,我们考虑第一个数 \\(x\\) :
如果 \\(x\\) 是 \\(nan\\),那相当于把一个 \\(nan\\) 置于最前,然后排序其余的数。
否则,相当于把所有剩下的 \\(< x\\) 的数从小到大放在 \\(x\\) 之前,并放置 \\(x\\) 。
那么这个过程可以使用优先队列来实现。代码如下:
AC_code
#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=5e5+10;
struct node
{
bool is;
int val;
}cun[N];
int t,n,cnt;
priority_queue<int,vector<int>,greater<int> >Q;
ii read()
{
int x=0; char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'n\')
{
f=0;
ch=getchar();
ch=getchar();
ch=getchar();
return -1;
}
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-1);
}
int main()
{
freopen("qsort.in","r",stdin);
freopen("qsort.out","w",stdout);
t=read();
while(t--)
{
n=read();
for(re i=1;i<=n+1;i++) cun[i]=(node){0,0};
for(re i=1;i<=n;i++)
{
int x=read();
if(x==-1) cun[i].is=1;
else {cun[i].val=x,Q.push(x);}
}
for(re i=1;i<=n;++i)
{
if(cun[i].is==1) printf("nan ");
else
{
while(Q.size() and Q.top()<cun[i].val) printf("%d ",Q.top()),Q.pop();
if(Q.size() and Q.top()==cun[i].val) printf("%d ",Q.top()),Q.pop();
}
}
printf("\\n");
}
return 0;
}
T3 混乱邪恶
思路:目前只会打部分分,说一下部分分的思路:我们设\\(f_{i,j0/1}\\)表示前\\(i\\)个数能否凑出和为\\(j\\),如果可以,那么最后一个数的位置是什么。那么我们就可以暴力转移了。考虑优化,我们显然可以滚掉一维,但是要注意,如果之前已经凑出过当前位置的数了,那么就不需要更新了,否则因为我利用的是之前的数进行的更新,当我修改之后,当我统计答案的时候就不对了。
代码如下:
AC_code
#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=1e6+10;
int n,m,sum;
int a[N],ans[N];
int f[N][2],pre[N][2];
bool flag;
ii read()
{
int x=0; char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'-\') f=0;
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?x:(-x);
}
signed main()
{
freopen("chaoticevil.in","r",stdin);
freopen("chaoticevil.out","w",stdout);
n=read(),m=read();
for(re i=1;i<=n;i++) a[i]=read(),sum+=a[i];
sum/=2;
for(re i=1;i<=n;i++) ans[i]=-1;
f[a[1]][0]=f[a[1]][1]=1;
for(re i=2;i<=n;i++)
{
for(re j=sum-a[i];j;j--)
{
if( (!f[j+a[i]][0]) and (f[j][0]) )
{
f[j+a[i]][0]=1;
f[j+a[i]][1]=i;
pre[j+a[i]][0]=f[j][1];
pre[j+a[i]][1]=j;
}
}
if(!f[a[i]][0])
{
f[a[i]][0]=1,f[a[i]][1]=i;
pre[a[i]][0]=pre[a[i]][1]=0;
}
if(f[sum][0]) {flag=1;break;}
}
if(flag)
{
printf("NP-Hard solved\\n");
ans[f[sum][1]]=1;
while(sum and pre[sum][0])
{
ans[pre[sum][0]]=1;
sum=pre[sum][1];
}
for(re i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\\n");
}
else printf("Chaotic evil\\n");
return 0;
}
T4 校门外歪脖树上的鸽子
咕了。
以上是关于noip多校21的主要内容,如果未能解决你的问题,请参考以下文章