【题解】电子科技大学第十九届 ACM 程序设计竞赛 - 初赛

第十四名 —— B站关注嘉然

A

动态规划

\(presum[i]\) 为原序列位置 \(i\) 之前 \(0\)\(1\) 个数的差值

\(f[i]\) 为最后一次在位置 \(i\) 操作,位置 \(i\) 之前 \(0\)\(1\) 个数的最大差值

\(f[i]=max\{f[j]-presum[j]+presum[i-3]\}\),其中 \(j<=i-3\)

\(d[i]\) 为最后一次在位置 \(i\) 操作,整个序列中 \(0\)\(1\) 个数的最大差值

\(d[i]=f[i]+presum[n]-presum[i]\)

考虑所有最后一次操作可能位置的差值 \(d[i]\),以及一次也没有进行操作的差值 \(presum[n]\)

答案即为 \(max\{max\{d[i]\},presum[n]\}\)

#include <cstdio>
#include <iostream>
using namespace std;
int n,m[100001],f[100001],presum[100001],premax[100001],ans;
int solve(){
    int ret=0;
    for (int i=1;i<=n;i++) presum[i]=presum[i-1]+(m[i]==0?1:-1);
    for (int i=3;i<=n;i++){
        f[i]=premax[i-3]+presum[i-3];
        premax[i]=max(premax[i-1],f[i]-presum[i]);
    }
    for (int i=3;i<=n;i++){
        f[i]+=presum[n]-presum[i];
        ret=max(ret,f[i]);
    }
    ret=max(ret,presum[n]);
    return ret;
}
int main(){
    scanf("%d\n",&n);
    for (int i=1;i<=n;i++){
        scanf("%c",&m[i]);
        m[i]-='0';
    }
    ans=solve();
    for (int i=1;i<=n;i++) m[i]=!m[i];
    ans=max(ans,solve());
    printf("%d",ans);
}

B

模拟

检测两个集合的关系,统计对应元素是否出现即可

#include <cstdio>
#include <set>
using namespace std;
int a[100001],b[100001];
int p1=0,p2=0,p3=0;
set<int> sa,sb;
int main(){
    int n,m;
    scanf("%d",&n);
    for (int i=0;i<n;i++){
        scanf("%d",&a[i]);
        sa.insert(a[i]);
    }
    scanf("%d",&m);
    for (int i=0;i<m;i++){
        scanf("%d",&b[i]);
        sb.insert(b[i]);
    }
    for (int i=0;i<n;i++){
        if (sb.count(a[i])==0)
            p1=1;
    }
    for (int i=0;i<m;i++){
        if (sa.count(b[i])==0)
            p2=1;
        if (sa.count(b[i])==1)
            p3=1;
    }
    if (p1==0&&p2==0){
        printf("A equals B");
    }else{
        if (p1==1&&p2==0){
            printf("B is a proper subset of A");
        }else{
            if (p1==0&&p2==1){
                printf("A is a proper subset of B");
            }else{
                if (p3==0)
                    printf("A and B are disjoint");
                else
                    printf("I am confused!");
            }
        }
    }
}

C

数论

用最小质因子 \(2\) 做桥梁即可将所有数联系起来,所以答案为 \(max\{2*(n以内的最大质数),n\}\)

#include <cstdio>
typedef long long ll;
const ll M=1000001;
ll t,n,pri[M],a,b;
ll ans[M];
int main(){
    for (ll i=2;i<=M;i++) pri[i]=1;
    for (ll i=2;i<=M;i++){
        if (pri[i]){
            for (ll j=i*i;j<=M;j+=i)
                pri[j]=0;
        }
    }
    b=2;
    for (ll i=3;i<=M;i++){
        if (pri[i]){
            a=i;
        }
        ans[i]=a*2;
    }
    scanf("%lld",&t);
    for (ll i=0;i<t;i++){
        scanf("%lld",&n);
        printf("%lld\n",ans[n]>n?ans[n]:n);
    }
}

D

字符串哈希

对字符串做哈希后用 map 统计出现次数即可

#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#define ll long long
using namespace std;
struct P{
    int c1,c2;
    friend bool operator< (const P &a,const P &b){
        return a.c1!=b.c1?a.c1<b.c1:a.c2>b.c2;
    }
}p[500001];
int cnt;
map<ll,int> s;
priority_queue<P> q;
ll calchash(char *s){
    int len=strlen(s);
    ll sum=0;
    for (int i=0;i<len;i++)
        sum+=131*sum+s[i];
    return sum;
}
char s1[500001][51],s2[500001][51];
ll shash[500001];
int main(){
    while(~scanf("%s %s",s1[cnt],s2[cnt])){
        shash[cnt]=calchash(s1[cnt]);
        if(s.count(shash[cnt])==0)
            s[shash[cnt]]=0;
        else
            s[shash[cnt]]++;
        p[cnt].c2=cnt;
        cnt++;
    }
    for (int i=0;i<cnt;i++){
        p[i].c1=s[shash[i]];
        q.push(p[i]);
    }
    while(!q.empty()){
        cout<<s1[q.top().c2]<<" "<<s2[q.top().c2]<<endl;
        q.pop();
    }
}

E

离散化

用优先队列做离散化之后模拟即可

#include <cstdio>
#include <queue>
#include <iostream>
using namespace std;
typedef pair<int,int> P;
const int N=200001;
priority_queue<P,vector<P>,greater<P> > q1;
priority_queue<int,vector<int>,greater<int> > q2;
int n,s,t,x=1,ans=0;
int main(){
    scanf("%d",&n);
    for (int i=0;i<n;i++){
        scanf("%d%d",&s,&t);
        q1.push(P(s,t));
    }
    while (!q1.empty()||!q2.empty()){
        if (q2.empty()) x=q1.top().first;
        while (!q1.empty()&&q1.top().first==x){
            P p=q1.top();q1.pop();
            q2.push(p.second);
        }
        while (!q2.empty()&&q2.top()<x) q2.pop();
        if (q2.top()>=x) {
            q2.pop();
            ans++;
            x++;
        }
    }
    printf("%d",ans);
}

F

广度优先搜索

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
struct P{
    int x,y,len;
    P(int ax,int ay,int alen){
        x=ax,y=ay,len=alen;
    }
};
int n,x,y,a,b,ans,map[1003][1003];
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
queue<P> q;
int main(){
    scanf("%d%d%d",&x,&y,&n);
    x+=501,y+=501;
    for (int i=0;i<n;i++){
        scanf("%d%d",&a,&b);
        a+=501,b+=501;
        map[a][b]=1;
    }
    q.push(P(501,501,0));
    while (!q.empty()){
        P k=q.front();
        if (k.x==x&&k.y==y) ans=k.len;
        for (int i=0;i<4;i++){
            int ax=k.x+dir[i][0],ay=k.y+dir[i][1];
            if (0<=ax&&ax<1003&&0<=ay&&ay<1003&&map[ax][ay]==0){
                map[ax][ay]=1;
                q.push(P(ax,ay,k.len+1));
            }
        }
        q.pop();
    }
    printf("%d",ans);
}

J

贪心

去掉绝对值符号后,比较大的 \(n/2\) 个数符号取正,比较小的 \(n/2\) 个数符号取负,靠中间的两个数特殊处理一下即可

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n,h[100001],ans,ans2;
int main(){
    scanf("%lld",&n);
    for (ll i=0;i<n;i++) scanf("%lld",&h[i]);
    sort(h,h+n);
    if (n%2==0){
        for (ll i=0;i<n/2;i++) ans-=h[i]*2;
        for (ll i=n/2;i<n;i++) ans+=h[i]*2;
        ans+=h[n/2-1]-h[n/2];
    }else{
        for (ll i=0;i<n/2;i++) ans-=h[i]*2;
        for (ll i=n/2;i<n;i++) ans+=h[i]*2;
        ans+=-h[n/2]-h[n/2+1];
        for (ll i=0;i<n/2+1;i++) ans2-=h[i]*2;
        for (ll i=n/2+1;i<n;i++) ans2+=h[i]*2;
        ans2+=h[n/2]+h[n/2-1];
        ans=max(ans,ans2);
    }
    printf("%lld",ans);
}

K

图论

维护每个节点 \(x\) 的子树大小 \(siz[x]\),所有子节点到节点 \(x\) 的路径和 \(len[x]\)

节点 \(1\) 连接到节点 \(i\) 时,形成一个基环外向树,设节点 \(x\) 的外向树大小为 \(subsiz[x]\)

基环上节点 \(i\) 的外向树大小为 \(subsiz[i]=siz[i]\),基环上其他节点 \(j\) 的外向树大小为 \(subsiz[j]=siz[j]-siz[son[j]]\),其中 \(son[j]\) 为基环上节点 \(j\) 的子节点

考虑基环上每个节点 \(x\) 的外向树对答案的贡献为 \(subsiz[x]*cirlen[x]+len[x]\),其中 \(cirlen[x]\) 为节点 \(x\) 通过基环到达节点 \(1\) 的最短路径长度

随着基环长度变化,基环上部分节点的 \(cirlen[x]\) 也会变化,但是考虑到会变化的区间是连续靠下方的半段,所以可以维护这一部分变化区间的 \(subsiz[x]\) 的和 \(downsum\)

每次更新直接对答案加上对应的区间和 \(downsum\) 即可用 \(O(1)\) 的复杂度维护答案中的 \(subsiz[x]*cirlen[x]\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=1e5+1;
int n,u,v,siz[N],len[N],idxsta[N],dep,substa[N],downsum,ans,fans=1e9;
int sz,head[N],vis[N];
struct E{
    int next,to;
}e[N*2];
void insert(int a,int b){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
}
void dfs(int x){
    vis[x]=1;
    siz[x]=1;
    len[x]=0;
    for (int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if (!vis[v]){
            dfs(v);
            siz[x]+=siz[v];
            len[x]+=len[v]+siz[v];
        }
    }
}
void solve(int x){
    dep++;
    vis[x]=1;
    idxsta[dep]=x;
    if (dep%2==1) downsum-=substa[dep/2+1];
    downsum+=siz[x];
    ans+=len[x];
    ans+=downsum;
    fans=min(ans,fans);
    ans-=downsum;
    ans-=len[x];
    downsum-=siz[x];
    if (dep%2==1) downsum+=substa[dep/2+1];
    for (int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if (!vis[v]){
            substa[dep]=siz[x]-siz[v];
            if (dep%2==1) downsum-=substa[dep/2+1];
            downsum+=substa[dep];
            ans+=len[x]-len[v]-siz[v];
            ans+=downsum;
            solve(v);
            ans-=downsum;
            ans-=len[x]-len[v]-siz[v];
            downsum-=substa[dep];
            if (dep%2==1) downsum+=substa[dep/2+1];
        }
    }
    dep--;
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        insert(u,v);
        insert(v,u);
    }
    dfs(1);
    memset(vis,0,sizeof(vis));
    solve(1);
    printf("%d",fans);
}

L

回文串

Manacher 结合前缀和,统计每种长度对应的字符串个数即可

#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const ll M=100001,K=1e9+7;
ll n,k,d[M],mul[M],cnt[M],sum=0,ans=1;
char s[M];
ll binpow(ll a, ll b, ll m) {
  a %= m;
  ll res = 1;
  while (b > 0) {
    if (b & 1) res = res * a % m;
    a = a * a % m;
    b >>= 1;
  }
  return res;
}
int main(){
    scanf("%lld%lld\n",&n,&k);
    scanf("%s",s);
    for (ll i = 0, l = 0, r = -1; i < n; i++) {
        ll k = (i > r) ? 1 : min(d[l + r - i], r - i);
        while (0 <= i - k && i + k < n && s[i - k] == s[i + k]) {
            k++;
        }
        d[i] = k--;
        if (i + k > r) {
            l = i - k;
            r = i + k;
        }
    }
    for (ll i=0;i<n;i++){
        d[i]=d[i]*2-1;
        cnt[d[i]]++;
    }
    for (ll i=n/2*2+1;i>=1;i-=2){
        sum+=cnt[i];
        if (k>sum){
            ans=ans*binpow(i,sum,K)%K;
            k-=sum;
        }else{
            ans=ans*binpow(i,k,K)%K;
            k=0;
        }
    }
    if (k>0) ans=-1;
    printf("%lld",ans);
}

N

贪心

选最小的 \(k/2\) 个坐标和最大的 \(k/2\) 个坐标即可

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n,k,x[100001],ans;
int main(){
    scanf("%lld%lld",&n,&k);
    for (ll i=0;i<n;i++) scanf("%lld",&x[i]);
    sort(x,x+n);
    for (ll i=0;i<k/2;i++)
        ans-=(k-1-i*2)*x[i];
    for (ll i=0;i<k/2;i++)
        ans+=(k-1-i*2)*x[n-1-i];
    printf("%lld",ans);
}
posted @ 2021-05-17 17:29  Byaidu  阅读(220)  评论(1编辑  收藏  举报