两个T3的难度较大node
平均代码量远大于去年省选ios
套路题考查居多算法
难度等级 1数据结构
$n^2$暴力能够拿到$60$分的优秀成绩函数
而后能够想到把区间异或转化为前缀两点异或优化
能够想到使用二分答案的方法+可持久化Trie解决,可是时间复杂度为$n\log^2 (4294967295) $ui
这是惟一一经过不了的$poly(\log)$作法,常数不够优秀的话会获得$60$分,卡一卡说不定能够$80$,理论上能卡到$100$spa
而后能够经过将二分放在可持久化Trie上,将复杂度降为$ n \log(4294967295)$,能够经过此题code
而后还有一种维护相似于超级钢琴的作法,我并不了解排序
对于我改题的时候,写的是一个可持久化Trie+堆的作法,咱们维护一个大根堆,将以$i$为结尾的区间中最大的推入堆
而后每次找到堆顶,查找对应右端点下一个比这个小一点的区间是多大,而后再推入堆便可
复杂度很显然,为:$(n+k)\log (4294967295)$
// luogu-judger-enable-o2 #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> using namespace std; #define N 500005 #define ll long long int n,K,now[N];unsigned a[N];long long ans; struct Trie { int ch[N*33][2],siz[N*33],cnt,rot[N]; inline void copy(int x,int y){siz[x]=siz[y];ch[x][0]=ch[y][0];ch[x][1]=ch[y][1];} inline void insert(unsigned x,int id) { int rt=++cnt,lst=rot[id-1],t;rot[id]=rt;copy(rt,lst); for(int i=31;~i;i--) { t=(x>>i)&1; ch[rt][t]=++cnt;rt=ch[rt][t];lst=ch[lst][t]; copy(rt,lst);siz[rt]++; } } inline int query(unsigned x,int id,int K) { int rt=rot[id],t;unsigned ret=0; for(int i=31;~i;i--) { if(!rt)return ret;t=(x>>i)&1; if(K>siz[ch[rt][!t]])K-=siz[ch[rt][!t]],rt=ch[rt][t]; else rt=ch[rt][!t],ret|=1u<<i; } return ret; } }tr; priority_queue<pair<unsigned ,int > >q; int main() { // freopen("xor.in","r",stdin); // freopen("xor.out","w",stdout); scanf("%d%d",&n,&K); for(int i=1;i<=n;i++)scanf("%u",&a[i]),a[i]^=a[i-1],tr.insert(a[i-1],i); for(int i=1;i<=n;i++)q.push(make_pair(tr.query(a[i],i,++now[i]),i));int t; while(K--)ans+=q.top().first,t=q.top().second,q.pop(),q.push(make_pair(tr.query(a[t],t,++now[t]),t)); printf("%lld\n",ans); }
长度较短,是一个相对简单的可持久化数据结构套路
难度等级 3
能够转化为一个经典的字符串问题,能够容易的想到,使用字符串hash拿到$40$分的成绩
而后能够想到使用Sa,找到知足某个位置以后的字符串是这个B串的rank区间,而后使用线段树优化建图来完成,能拿到$80$分的优秀成绩,而后我就不太会了,而且代码量较高,并不能在很短的时间内完成
同时,能够想到使用Sam解决,在后缀树上主席树合并优化建图,而后再倍增定位节点一样能够拿到$80$分的优秀成绩,可是一样,代码复杂度较高
在此基础上,咱们发现,线段树合并这步没有意义,直接在后缀树上拆点连边,后缀树优化建图,就能够拿到$80$分的优秀成绩
而后在此基础上,将每一个节点的长度和编号按照长度从小到大,先b后a的顺序排序,而后前缀优化建图,便可拿到$100$分的优秀成绩
代码复杂度相对不高,总体考查了选手对于字符串经典模型的转化,前缀优化建图,后缀树的基本应用等知识,对字符串方面薄弱的选手是一个沉重的打击
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> using namespace std; #define ll long long int n,na,nb,tot,cnt_SAM,oth,p[400005];char s[200005]; namespace Graph { const int N = 1000005; struct node{int to,next;}e[N<<2];int head[N],cnt,in[N],q[N],a[N];ll f[N]; void add(int x,int y){e[cnt]=(node){y,head[x]};head[x]=cnt++;in[y]++;} void init(){cnt=0;memset(head,-1,sizeof(int)*(tot+3));memset(in,0,sizeof(int)*(tot+3));memset(a,0,sizeof(int)*(tot+3));} ll tsort() { int l=0,r=0;for(int i=1;i<=tot;i++)if(!in[i])q[r++]=i; while(l<r) { int x=q[l++]; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!(--in[to1]))q[r++]=to1; } } if(r!=tot)return -1;ll ans=0; for(int j=r-1;~j;j--) { int x=q[j];f[x]=a[x]; for(int i=head[x];i!=-1;i=e[i].next)f[x]=max(f[x],f[e[i].to]+a[x]); ans=max(ans,f[x]); }return ans; } } inline bool cmp(const pair<int ,int > &a,const pair<int ,int >&b){return a.first==b.first?a.second>b.second:a.first<b.first;} // SAM namespace Sam { const int N = 400005; int trs[N][26],len[N],fa[N],pos[N],lst,cnt;vector<pair<int ,int > >s[N]; inline void init(){lst=cnt=1;memset(trs[1],0,sizeof(trs[1]));fa[1]=len[1]=0;} inline int new_node(){cnt++;fa[cnt]=len[cnt]=0;memset(trs[cnt],0,sizeof(trs[cnt]));s[cnt].clear();return cnt;} inline void insert(int x,int id) { int np=new_node(),nq,p=lst,q;len[np]=len[p]+1;pos[id]=np;lst=np; for(;p&&!trs[p][x];p=fa[p])trs[p][x]=np; if(!p)fa[np]=1;else if(len[q=trs[p][x]]==len[p]+1)fa[np]=q; else { len[nq=new_node()]=len[p]+1;fa[nq]=fa[q];fa[q]=fa[np]=nq; memcpy(trs[nq],trs[q],sizeof(trs[nq])); for(;p&&trs[p][x]==q;p=fa[p])trs[p][x]=nq; } } struct node{int to,next;}e[N];int head[N],edge_cnt; inline void add(int x,int y){e[edge_cnt]=(node){y,head[x]};head[x]=edge_cnt++;} int f[N][19]; void dfs(int x,int from) { f[x][0]=from;for(int i=1;i<19;i++)f[x][i]=f[f[x][i-1]][i-1]; for(int i=head[x];i!=-1;i=e[i].next)dfs(e[i].to,x); } inline void build(){cnt_SAM=cnt;memset(head,-1,sizeof(int)*(cnt+3));edge_cnt=0;for(int i=2;i<=cnt;i++)add(fa[i],i);dfs(1,0);} inline void query(int l,int p,int id) { int x=pos[p]; for(int i=18;~i;i--)if(len[f[x][i]]>=l)x=f[x][i]; s[x].push_back(make_pair(l,id)); } inline void build_Graph() { oth=0; for(int i=2;i<=cnt;i++) { sort(s[i].begin(),s[i].end(),cmp);int lim=s[i].size(),lst=i; for(int j=0;j<lim;j++) { int x=s[i][j].second+cnt;p[s[i][j].second]=x; if(j==0)Graph::add(i,x);else Graph::add(s[i][j-1].second+cnt,x); lst=x; if(s[i][j].second<=na) { int u=na+nb+cnt+(++oth); p[s[i][j].second]=u;Graph::add(x,u); Graph::a[u]=s[i][j].first; } } for(int j=head[i];j!=-1;j=e[j].next)Graph::add(lst,e[j].to); } } } int T,Case; void solve() { scanf("%s",s+1);n=strlen(s+1);Sam::init(); for(int i=n;i;i--)Sam::insert(s[i]-'a',i);Sam::build();scanf("%d",&na); for(int i=1,l,r;i<=na;i++)scanf("%d%d",&l,&r),Sam::query(r-l+1,l,i);scanf("%d",&nb); for(int i=1,l,r;i<=nb;i++)scanf("%d%d",&l,&r),Sam::query(r-l+1,l,i+na); tot=(cnt_SAM)+(na<<1)+nb;Graph::init();Sam::build_Graph(); int m;scanf("%d",&m);for(int x,y;m--;Graph::add(p[x],p[y+na]))scanf("%d%d",&x,&y); printf("%lld\n",Graph::tsort()); } int main(){scanf("%d",&T);while(T--)Case++,solve();}
能够显然的发现,我就是那个被打击的
难度等级 5
这个题不太能作吧?
1_998244353直接费马小定理+快速幂就能够了
1?暴力找一个模数便可,模数为$1145141$
1?+经过找到输入数据中,输入最接近的两个数,找到对应答案里的位置,而后经过观察输出数据找到模数的大体区间,而后验证便可,模数为$5211600617818708273$
1wa_998244353,观察能够发现,这个是在乘法的过程当中爆$int$了,而后第一个能够直接暴力每次乘以$19$,第二个的话,一种是能够分块打表,也能够经过找循环节搞定
2p的话,就是若是是质数,该位为$p$不然为$.$,而后8是直接线筛,9是线筛+根号求,10是$Miller-Rabin$
2u的话,就是莫比乌斯函数,前两个相似上面的前两个,最后一个打表+线筛便可
2g的话,就是原根,我不会
没写
难度等级 4
能够说是一个很是好的高维DP问题,数据范围具备迷惑性,让人以为暴力能过,而后本人尝试卡常,而后失败了,最后只拿到了$50$分
咱们先给出一个DP方程:$f[i][j][k][0/1]$表示前$i$个学校,蓝阵营有$j$我的,鸭派系有$k$我的的方案数,而后每次直接转移便可,特判很少,属于能够接受的范围
而后咱们发现$k$比较小,因此咱们考虑提出一个复杂度阈值有关$k$的作法
那么咱们先从$k=0$入手
对于$k=0$的状况,咱们发现,不论这个城市选择哪个阵营,如何选择,和学校选择哪一个派系没有任何关系
由于对于每一个阵营,都有两个派系能够选,而且由于$k=0$因此具体选哪一个阵营没啥意义
因此答案即为$选择派系的分组数\times 选择阵营的分组数$,这两个东西分别DP一下便可
而后咱们考虑$k \neq 0$的状况,能够发现,必定最多只有$k$个城市和$k$个学校不会被访问
这样咱们发现能够单独对这$k$个城市DP,而后剩下的的东西作一遍$k=0$的状况便可
可是咱们发现,这样若是这$k$个城市的学校数=n的话,那复杂度就依然为$nm^2$就没有发生任何优化
那么咱们考虑将这个问题和$k=0$结合起来
咱们发现,对于这样的对于存在约束的学校,只须要正常DP一下,而后对于没有约束的学校,你只需钦定它的阵营,并不须要钦定它的派系,派系只须要最后搞一下就能够了
而后这样的话,就能够作到:$O(km^2 + n\times m)$解决了
可能会被卡常,而后稍微优化一下复杂度上届便可
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> using namespace std; #define N 2505 #define ll long long #define mod 998244353 int f[N][305],g[N][305],f1[N],f0[N]; int n,c,k,C0,C1,D0,D1,M,S,bel[N],sz[N],hat[N],ht[N],s[N]; void get_f0() { f0[0]=1; for(int i=1;i<=n;i++)if(hat[i]==-1) for(int j=M;j>=sz[i];j--)f0[j]=(f0[j]+f0[j-sz[i]])%mod; for(int j=1;j<=M;j++)(f0[j]+=f0[j-1])%=mod; } void get_f1() { f1[0]=1; for(int i=1;i<=c;i++)if(s[i]&&ht[i]==-1) for(int j=M;j>=s[i];j--)f1[j]=(f1[j]+f1[j-s[i]])%mod; for(int j=1;j<=M;j++)(f1[j]+=f1[j-1])%=mod; } inline int get(int v0,int v1) { int l0=max(C0-v0,0),r0=min(C1-v0,M),l1=max(D0-v1,0),r1=min(D1-v1,M); if(l0>r0||l1>r1)return 0;return (ll)(f1[r0]-(l0?f1[l0-1]:0))*(f0[r1]-(l1?f0[l1-1]:0))%mod; } void solve() { memset(f,0,sizeof(f));memset(g,0,sizeof(g));memset(hat,-1,sizeof(hat));S=0; memset(ht,-1,sizeof(ht));memset(f1,0,sizeof(f1));memset(f0,0,sizeof(f0));memset(s,0,sizeof(s)); scanf("%d%d%d%d%d%d",&n,&c,&C0,&C1,&D0,&D1);M=max(max(C0,C1),max(D0,D1)); for(int i=1;i<=n;i++)scanf("%d%d",&bel[i],&sz[i]),s[bel[i]]+=sz[i],S+=sz[i]; scanf("%d",&k);for(int i=1,x,y;i<=k;i++)scanf("%d%d",&x,&y),ht[bel[x]]=hat[x]=y; get_f0();get_f1();C1=max(S-C1,0),D1=max(S-D1,0);swap(C0,C1);swap(D0,D1); if(C0>C1||D0>D1)return puts("0"),void();g[0][0]=1;int S0=0,S1=0; for(int i=1;i<=c;i++)if(ht[i]!=-1) { for(int j=S0;~j;j--)for(int k=S1;~k;k--)f[j][k]=g[j][k]; for(int j=1;j<=n;j++)if(bel[j]==i&&hat[j]!=-1) { S1+=sz[j]; if(hat[j]==1)for(int k=S0;~k;k--){for(int l=S1;l>=sz[j];l--)g[k][l]=g[k][l-sz[j]];for(int l=0;l<sz[j];l++)g[k][l]=0;} else if(hat[j]!=0)for(int k=S0;~k;k--)for(int l=S1;l>=sz[j];l--)g[k][l]=(g[k][l]+g[k][l-sz[j]])%mod; if(hat[j]==3)for(int k=S0;~k;k--){for(int l=S1;l>=sz[j];l--)f[k][l]=f[k][l-sz[j]];for(int l=0;l<sz[j];l++)f[k][l]=0;} else if(hat[j]!=2)for(int k=S0;~k;k--)for(int l=S1;l>=sz[j];l--)f[k][l]=(f[k][l]+f[k][l-sz[j]])%mod; } S0+=s[i];S0=min(S0,M); for(int j=S0;~j;j--)for(int k=S1;~k;k--)g[j][k]=((j>=s[i]?g[j-s[i]][k]:0)+f[j][k])%mod; } int ans=0; for(int i=0;i<=S0;i++)for(int j=0;j<=S1;j++)ans=(ans+(ll)g[i][j]*get(i,j))%mod; printf("%d\n",(ans+mod)%mod); } int main(){int T;scanf("%d",&T);while(T--)solve();}
难度评级 2
一道不失幽默的树上贪心数据结构问题
其实看一看就能发现,对每一个子树,按照从大到小排序后,按位置合并便可
这样就能拿到$60$分的优秀成绩了
而后正解就是上述算法优化一下便可
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> #include <set> using namespace std; #define N 200005 #define ll long long multiset<int ,greater<int > >s[N]; vector<int >v; struct node{int to,next;}e[N];int head[N],cnt,a[N],n,idx[N]; void add(int x,int y){e[cnt]=(node){y,head[x]};head[x]=cnt++;} void merge(int x,int y) { if(s[x].size()<s[y].size())swap(s[x],s[y]); for(;s[y].size();s[x].erase(s[x].begin()),s[y].erase(s[y].begin())) v.push_back(max(*s[x].begin(),*s[y].begin())); for(int i=0;i<v.size();i++)s[x].insert(v[i]); v.clear(); } void dfs(int x) { for(int i=head[x];i!=-1;i=e[i].next) dfs(e[i].to),merge(x,e[i].to); s[x].insert(a[x]); } int main() { scanf("%d",&n);memset(head,-1,sizeof(head)); for(int i=1;i<=n;i++)scanf("%d",&a[i]);for(int i=2,x;i<=n;i++)scanf("%d",&x),add(x,i); dfs(1);long long ans=0; while(s[1].size())ans+=*s[1].begin(),s[1].erase(s[1].begin()); printf("%lld\n",ans); }
不会,滚