2019浙江省赛题解
A 权值线段树+并查集+二分
这道题的解题思路比较好像,但是算法不太好想到。首先我们注意到对于询问,最小的情况比较简单,每次连边都贡献一个答案,这是最小的。对于最大的,我们应该考虑先将每个连通块都变成
完全图,然后每次选最大和次大的两个集合进行合并,这样是最消耗边的。
主要考虑如何处理这样的情况,集合的合并使用并查集来合并,同时我们可以维护一个遍历表示将当前所有的连通块都变成完全图需要的边数,之后只需要考虑如何快速的找到具有大小关系的并且
快速得知集合信息,显然需要一个数据结构去维护。这里我们考虑使用权值线段树,每个点表示集合大小x的集合,我们所需要的信息是集合个数,讲这些集合变成完全联通的边数,要做的这个,还需要
维护集合点数来利用乘法原理处理。之后在权值线段树上二分,考虑取右边的这块,这是最优的,去不了再去左边。到了叶子节点之后,我们发现每个大小的叶子节点之中不一定全部集合都用的到
因此继续二分,这样就能得到答案。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=2e6+10; const int M=2e6+10; const int inf=0x3f3f3f3f; const ll mod=998244353; struct node{ int l,r; ll ecnt; ll vcnt; ll num; }tr[N]; ll cal[N],lack; int p[N]; ll sz[N],num[N]; ll pv,pe,pnum; void build(int u,int l,int r){ if(l==r){ tr[u]={l,r}; } else{ tr[u]={l,r}; int mid=l+r>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); } } void pushup(int u){ tr[u].num=tr[u<<1].num+tr[u<<1|1].num; tr[u].vcnt=tr[u<<1].vcnt+tr[u<<1|1].vcnt; tr[u].ecnt=tr[u<<1].ecnt+tr[u<<1|1].ecnt+tr[u<<1].vcnt*tr[u<<1|1].vcnt; } void modify(int u,int l,int k){ if(tr[u].l==tr[u].r){ tr[u].num+=k; tr[u].vcnt+=k*l; tr[u].ecnt=cal[tr[u].vcnt]-cal[l]*tr[u].num; return ; } int mid=tr[u].l+tr[u].r>>1; if(l<=mid) modify(u<<1,l,k); else modify(u<<1|1,l,k); pushup(u); } int find(int x){ if(x!=p[x]){ p[x]=find(p[x]); } return p[x]; } ll ans=0,target; void query(int u){ if(tr[u].l==tr[u].r){ ll k=tr[u].l; int l=1,r=tr[u].num; while(l<r){ int mid=l+r>>1; if(pe+pv*k*mid+cal[k*mid]-cal[k]*mid>=target){ r=mid; } else{ l=mid+1; } } pnum+=l; return ; } int mid=tr[u].l+tr[u].r>>1; if(tr[u<<1|1].ecnt+pe+pv*tr[u<<1|1].vcnt>=target) query(u<<1|1); else{ pe+=tr[u<<1|1].ecnt+pv*tr[u<<1|1].vcnt; pv+=tr[u<<1|1].vcnt; pnum+=tr[u<<1|1].num; query(u<<1); } } int main(){ //ios::sync_with_stdio(false); int t; cin>>t; int i; for(ll i=1;i<=100001;i++){ cal[i]=1ll*(i-1)*i/2; } while(t--){ int n,q; lack=0; scanf("%d%d",&n,&q); for(i=0;i<=n;i++){ p[i]=i; sz[i]=1; num[i]=0; } build(1,1,n); modify(1,1,n); ll cnt=n; while(q--){ int opt; scanf("%d",&opt); if(opt==1){ int a,b; scanf("%d%d",&a,&b); int pa=find(a),pb=find(b); if(pa!=pb){ lack-=(cal[sz[pa]]-num[pa]); lack-=(cal[sz[pb]]-num[pb]); modify(1,sz[pa],-1); modify(1,sz[pb],-1); sz[pb]+=sz[pa]; p[pa]=pb; num[pb]+=num[pa]+1; modify(1,sz[pb],1); lack+=(cal[sz[pb]]-num[pb]); cnt--; } else{ num[pa]++; lack--; } } else{ ll x; scanf("%lld",&x); printf("%lld ",x>=cnt-1?1:cnt-x); if(x<=lack){ printf("%lld\n",cnt); } else{ x-=lack; target=x; pe=pv=pnum=0; query(1); printf("%lld\n",cnt-pnum+1); } } } } return 0; }
B
推公式可得答案,注意判断分母不可为0
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=1e6+10; const int M=2e6+10; const int inf=0x3f3f3f3f; ll a[N]; set<ll> s; int num[N]; int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ ll n,x,y; cin>>n>>x>>y; int i; s.clear(); for(i=1;i<=n;i++){ cin>>a[i]; num[a[i]]=0; } ll tmp1=0,tmp2=0; for(i=1;i<=n;i++){ tmp1+=i*a[i]; tmp2+=i*a[i]*a[i]; s.insert(a[i]); num[a[i]]++; } ll d1=tmp1-x,d2=tmp2-y; ll ans=0; if(d1==0&&d2==0){ for(auto it=s.begin();it!=s.end();it++){ ll d=*it; if(num[d]){ ans+=(ll)num[d]*(num[d]-1)/2; } } } else if(d1!=0&&d2!=0){ if(d2%d1==0) { ll q=d2/d1; for(int i=1;i<=n;i++) { if(q-a[i]>0&&num[q-a[i]]>0) { ll nape=a[i]-(q-a[i]); if(nape!=0&&d1%nape==0) { int k=i-d1/nape; if(k>i&&k<=n&&a[k]==q-a[i]) ans++; } } } } else{ ans=0; } } else{ ans=0; } cout<<ans<<endl; } return 0; }
C 构造
对于构造题且有不合法状态,先找到什么是不合法状态。对于本题,感性理解一下,如果有一个数大于一半那显然不合法,否则就能构造出合法方案
在构造的时候使用贪心,因为越小越好,但是有一种情况填的数是固定的,也就是比如i我手里还剩下要填的i的个数+我之后不能填的i的位置等于剩下的位置时
当前数必须填他,注意这里不可能出现非法状态,因为我们前面已经把非法判断过了。在进行比较的时候,我们得先把当前位置所属值的信息进行改变,因为当前位不能选他。
如果不是先处理,可能意外选到他。这种算法的正确性在于,我们能够想到当前一定能构造出合法解,并且我们按照这种算法构造的是最优的,所以这种算法是正确的
#include<bits/stdc++.h> using namespace std; const int M=1e5+10; int a[M],b[M]; int c[M],cc[M]; typedef pair<int,int> P; set<P> s1,s2; set<P>::iterator it,mx; int main(){ int t,n; cin>>t; while(t--){ scanf("%d",&n); for(int i=1;i<=n;i++){ a[i]=b[i]=c[i]=cc[i]=0; } s1.clear();s2.clear(); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); c[a[i]]++;cc[a[i]]+=2; } for(int i=1;i<=n;i++){ if(c[i]){ s1.insert(P(i,c[i])); s2.insert(P(cc[i],i)); } } //int temp=(*(--s2.end()))->first; it=s2.end(); it--; int temp=it->first; if(temp>n){ printf("Impossible\n"); continue; } for( int i=1;i<=n;i++){ s2.erase(P(cc[a[i]],a[i])); s2.insert(P(--cc[a[i]],a[i])); it=s1.begin(); mx=s2.end(); mx--; if(mx->first==n-i+1){ b[i]=mx->second; } else { if(a[i]==it->first)it++; b[i]=it->first; } s1.erase(P(b[i],c[b[i]])); c[b[i]]--; s2.erase(P(cc[b[i]],b[i])); cc[b[i]]--; if(c[b[i]])s1.insert(P(b[i],c[b[i]])); if(cc[b[i]])s2.insert(P(cc[b[i]],b[i])); } cout<<b[1]; for(int i=2;i<=n;i++){ printf(" %d",b[i]); } printf("\n"); } }
E
考虑每个点最多移动一次,而他移动的条件是权值比他大的数字在他前面或者权值比他大的数字进行了移动
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=2e5+10; const int M=2e6+10; const int inf=0x3f3f3f3f; struct node{ int x; int id; }s[N]; bool cmp(node a,node b){ if(a.x==b.x) return a.id<b.id; return a.x<b.x; } int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ int n; int i; cin>>n; for(i=1;i<=n;i++){ cin>>s[i].x; s[i].id=i; } sort(s+1,s+1+n,cmp); int ans=0; int sign=0; int tmp=1000000; int ok=0; int tmp1=0; int tmp2=0; s[0].id=1000000; for(i=n-1;i>=1;i--){ if(s[i].x!=s[i+1].x){ tmp=min(tmp,s[i+1].id); ok|=tmp1; tmp1=0; } if(s[i].id>tmp){ tmp2=1; } if(tmp2||ok){ ans++; } if(tmp2||ok){ tmp1=1; } tmp2=0; } cout<<ans<<endl; } return 0; }
F 签到
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=2e5+10; const int M=2e6+10; const int inf=0x3f3f3f3f; set<char> m1; int main(){ ios::sync_with_stdio(false); int t; cin>>t; m1.insert('a'); m1.insert('e'); m1.insert('i'); m1.insert('o'); m1.insert('u'); m1.insert('y'); while(t--){ string s; cin>>s; int i; for(i=0;i<(int)s.size();i++){ if(i!=0&&m1.count(s[i])) continue; cout<<s[i]; } cout<<endl; } return 0; }
G 签到
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=2e5+10; const int M=2e6+10; const int inf=0x3f3f3f3f; int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ int n; cin>>n; int i; for(i=n;;i++){ if((i%7==0)&&(i%4!=0)){ cout<<i<<endl; break; } } } return 0; }
H 模拟题,只要枚举每个位置进行删除判断大小取max
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=2e5+10; const int M=2e6+10; const int inf=0x3f3f3f3f; ll a[N]; int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ int n; cin>>n; int i; for(i=1;i<=n;i++) cin>>a[i]; ll sum=0; if(i==1||i==2){ cout<<0<<endl; continue; } a[0]=1e18; a[n+1]=1e18; for(i=2;i<n;i++){ if(a[i]>a[i-1]&&a[i]>a[i+1]) sum++; } ll tmp=0; for(int i=1; i<=n-1; i++){ long long ans=0; long long ans1=0; if(a[i-1]>a[i+1]&&a[i-1]>a[i-2]) ans++; if(a[i+1]>a[i-1]&&a[i+1]>a[i+2]) ans++; if(a[i-1]>a[i-2]&&a[i-1]>a[i]) ans1++; if(a[i+1]>a[i+2]&&a[i+1]>a[i]) ans1++; if(a[i]>a[i-1]&&a[i]>a[i+1]) ans1++; tmp=max(tmp,ans1-ans); } cout<<sum-tmp<<endl; } return 0; }
I 找规律
一看数字这么大,显然是打表找规律,打完表后就发现答案了
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <map> #include <set> #include <vector> #include <string> #include <cstring> #define eps 1e-8 using namespace std; typedef long long ll; static const int MAX_N = 1e4 + 5; static const ll Mod = 233; static const int N = 105; static const int INF = 0x3f3f3f3f; char s1[MAX_N], s2[MAX_N]; int main(){ // freopen("input.txt", "r", stdin); // freopen("output.txt", "w", stdout); int T; scanf("%d", &T); while(T--){ scanf("%s%s", s1, s2); int l = 0, r = 0; for(int i = 0; s1[i]; ++i) l += s1[i] - '0'; for(int i = 0; s2[i]; ++i) r += s2[i] - '0'; if(l % 3 == 2 && r % 3 != 1 || l % 3 != 2 && r % 3 == 1) puts("1"); else puts("0"); } return 0; }
J 并查集+优先队列
观察到进行并查集后,每个集合只有一个点会不开心,之后进行优先队列取最小即可,虽然开心没有传递性,但是我们可以用vector存,找到合法答案
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=1e6+10; const int M=2e6+10; const int inf=0x3f3f3f3f; int p[N]; vector<int> g[N]; int ans[N]; int cnt=0; int st[N]; int find(int x){ if(x!=p[x]){ p[x]=find(p[x]); } return p[x]; } void bfs(int u){ priority_queue<int,vector<int>,greater<int>> q; q.push(0); while(q.size()){ int t=q.top(); q.pop(); if(!st[t]){ st[t]=1; ans[++cnt]=t; for(int i=0;i<g[t].size();i++){ if(!st[g[t][i]]){ q.push(g[t][i]); } } } } } int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--){ int n,m; cin>>n>>m; int i; cnt=0; for(i=0;i<=n;i++){ p[i]=i; g[i].clear(); st[i]=0; } while(m--){ int a,b; cin>>a>>b; g[a].push_back(b); g[b].push_back(a); int pa=find(a),pb=find(b); if(pa!=pb){ if(pb<pa){ p[pa]=pb; } else{ p[pb]=pa; } } } int sum=0; for(i=1;i<=n;i++){ if(p[i]==i){ sum++; g[0].push_back(i); } } bfs(0); cout<<sum<<endl; for(i=2;i<=cnt;i++){ if(i==cnt){ cout<<ans[i]<<endl; } else cout<<ans[i]<<" "; } } return 0; }
K 字符串
显然如果两个字符串相等,用马拉车算法求回文串个数,如果不等,找到最两边的不等后,看看能否往外扩展
#include <iostream> #include <algorithm> #include <cstring> #include <string> #include <cstdio> using namespace std; const int N = 4e6 + 7; char s1[N], s2[N], s[N]; int p[N]; int init(int len) { s[0] = '$', s[1] = '#'; int leng = 1; for (int i = 1; i <= len; ++i) { s[++leng] = s1[i]; s[++leng] = '#'; } s[++leng] = '@'; s[++leng] = '\0'; return leng-2; } void manacher(int len) { int id = 0, mx = 0; for (int i = 1; i <= len; ++i) { p[i] = i < mx ? min(p[2*id-i], mx-i) : 1; while (s[i + p[i]] == s[i - p[i]]) ++p[i]; if (mx < i + p[i]) mx = i + p[i], id = i; } } int main() { int t; scanf ("%d", &t); while (t--) { scanf ("%s %s", s1+1, s2+1); int len = strlen(s1+1), pos1 = -1, pos2 = -1; for (int i = 1; i <= len; ++i) { if (s1[i] != s2[i]) { pos1 = i; break; } } for (int i = len; i >= 1; --i) { if (s1[i] != s2[i]) { pos2 = i; break; } } if (pos1 == -1) { len = init(len); manacher(len); long long ans = 0; for (int i = 1; i <= len; ++i) ans += p[i]/2; printf ("%lld\n", ans); } else { int flag = 1; for (int i = pos1; i <= pos2; ++i) { if (s1[i] != s2[pos2 - (i-pos1)]) { flag = 0; break; } } if (!flag) { puts("0"); } else { long long ans = 0; while (s1[pos1] == s2[pos2] && s1[pos2] == s2[pos1] && pos1 >= 1 && pos2 <= len) { --pos1, ++pos2, ++ans; } printf ("%lld\n", ans); } } } return 0; }