2018 南京区域赛

A - Adrien and Austin

\(n\) 为奇数时先手必胜;为偶数时,若 \(k\leq1\) ,则后手必胜,否则先手必胜。注意特判 \(n=0\)

代码:

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    if(n==0)
    {
        printf("Austin\n");
        return 0;
    }
    if(n%2==0&&k<=1)
        printf("Austin\n");
    else
        printf("Adrien\n");
    return 0;
}

I - Magic Potion

  一开始想的是二分图匹配,先进行一趟最大匹配,求出 \(ans\)。然后把匹配的点标记掉,再求一趟最大匹配,结果为 \(res\)。最终结果为:\(ans+min(k,res)\)。但一直 \(WA\) 在第 \(7\) 个点。因为,第一遍求最大匹配时,最大匹配并非是唯一的,所以导致第二遍求的时候,就不一定是最大值。
  所以,改用最大流来求解。
建图如下:(同时考虑人数和药水数量的限制)

代码:

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int inf=0x3f3f3f3f;
const int N=1100;
struct node
{
    int to,val,rev;
};
vector<node>pic[N];
queue<int>que;
int layer[N],iter[N];
int V,n,m,k;
bool bfs()
{
    for(int i=1;i<=V;i++) layer[i]=-1;
    while(!que.empty()) que.pop();
    que.push(1);
    layer[1]=0;
    while(!que.empty())
    {
        int now=que.front();
        que.pop();
        for(int i=0;i<pic[now].size();i++)
        {
            node tmp=pic[now][i];
            if(layer[tmp.to]<0&&tmp.val>0)
            {
                layer[tmp.to]=layer[now]+1;
                que.push(tmp.to);
                if(tmp.to==V) return 1;
            }
        }
    }
    return 0;
}
int dfs(int v,int c)
{
    if(v==V) return c;
    for(int &i=iter[v];i<pic[v].size();i++)
    {
        node &tmp=pic[v][i];
        if(layer[tmp.to]>layer[v]&&tmp.val>0)
        {
            int d=dfs(tmp.to,min(c,tmp.val));
            if(d>0)
            {
                tmp.val-=d;
                pic[tmp.to][tmp.rev].val+=d;
                return d;
            }
        }
    }
    return 0;
}
int dinic()
{
    int max_flow=0,f=0;
    while(bfs())
    {
        for(int i=1;i<=V;i++) iter[i]=0;
        while((f=dfs(1,inf))>0)
            max_flow+=f;
    }
    return max_flow;
}
void addedge(int v,int u,int w)
{
    pic[v].pb(node{u,w,pic[u].size()});
    pic[u].pb(node{v,0,pic[v].size()-1});
}
int main()
{
    int x,t;
    scanf("%d%d%d",&n,&m,&k);
    V=n+m+3;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&t);
        for(int j=1;j<=t;j++)
        {
            scanf("%d",&x);
            addedge(i+2,x+2+n,2);
        }
    }
    addedge(1,2,k);
    for(int i=3;i<=n+2;i++)
    {
        addedge(2,i,1);
        addedge(1,i,1);
    }
    for(int i=n+2+1;i<=2+n+m;i++) addedge(i,V,1);
    printf("%d\n",dinic());
    return 0;
}

G - Pyramid

\(n^3\) 打表,找规律:
如下图:

可以发现后面为等差数列,从后向前推。
所以:

\[f[n]=1+\sum_{i=1}^{n-1}{[4+\sum_{j=1}^{i-1}{6+\frac{(j-6)*(j-1)}{2}}]} \]

一层一层拆开,利用公式:
\(1^2+2^2+...+n^2=\frac{n*(n+1)*(2*n+1)}{6}\)

\(1^3+2^3+...+n^3=(1+2+3+...+n)^2\)

最后可以化简出:

\[f[n]=n+\frac{1}{6}*[\frac{n*(n-1)}{2}]^2+\frac{n*(n-1)*(2*n-1)}{6}+\frac{11*n*(n-1)}{12} \]

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const ll inv2=500000004;
const ll inv6=166666668;
const ll inv12=83333334;
ll f1(ll x)
{
    return x*(x-1)%mod*inv2%mod;
}
ll f2(ll x)
{
    ll res=(x-1)*x%mod*(2*x-1)%mod*inv6%mod;
    return res;
}
int main()
{
    int t;
    ll n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld",&n);
        ll ans=n+f1(n)*f1(n)%mod*inv6%mod+f2(n)+11*n%mod*(n-1)%mod*inv12%mod;
        printf("%lld\n",(ans%mod+mod)%mod);
    }
    return 0;
}

发现网上还有更加简单的公式。
同样基于上述的表。\(4\) 次作差后差值恒定为 \(1\),说明递推式为:\(f(n)=a*n^4+b*n^3+c*n^2+d*n+e\),已知前 \(5\) 项,即可求出。

\[f[n]=\frac{n^4+6*n^3+11*n^2+6*n}{24} \]

下面基于差分来解决此题:

首先,前 \(7\) 项为:1 5 15 35 70 126 210
在前面加上 \(f(0)=0\)
可以得到差分表:

所以,根据图中的 ‘利用差分表求高级等差数列的前n项和’。
可以推出:(全0行的前面行的第一个数为组合数前面的系数)
\(S(n)=0*C(n+1,1)+1*C(n+1,2)+3*C(n+1,3)+3*C(n+1,4)+1*C(n+1,5)\)
\(=\frac{n*(n+1)*(n+2)*(n+3)*(n+4)}{120}\)

作差可以求出 \(f(n)=S(n)-S(n-1)=\frac{n*(n+1)*(n+2)*(n+3)}{24}\)

Problem J. Prime Game

考虑每个质因子对于整体答案的贡献。
\(p\) 个位置上的数,其包含的任意一个素因子,它原本应当产生的贡献有 \((n−p+1)⋅p\)。但考虑到重复,记该素因子上一次出现的位置为 \(q\),则此时的贡献为:\((p-q)*(n-p+1)\)。其他的就是质因子分解,筛出 \(1e3\) 以内的素数,共 \(168\) 个,用于素因子分解。

代码:

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e6+6;
const int maxn=1e3+5;
int prime[200],a[N],last[N];
int cnt;
bool vis[maxn];
vector<int>fac[N];
void euler()
{
    cnt=0;
    for(int i=2;i<=1e3;i++)
    {
        if(!vis[i]) prime[++cnt]=i;
        for(int j=1;j<=cnt&&prime[j]*i<=1e3;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
}
void divide(int num,int k)
{
    for(int i=1;i<=cnt&&prime[i]*prime[i]<=num;i++)
    {
        if(num%prime[i]==0)
        {
            fac[k].pb(prime[i]);
            while(num%prime[i]==0) num/=prime[i];
        }
    }
    if(num>1) fac[k].pb(num);
}
int main()
{
    euler();
    int n,a;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a);
        divide(a,i);
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ll res=0;
        for(int j=0;j<fac[i].size();j++)
        {
            int t=fac[i][j];
            if(last[t]==0) res=1LL*i*(n-i+1);
            else res=1LL*(i-last[t])*(n-i+1);
            last[t]=i;
            ans+=res;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

Problem D. Country Meow

最大值最小化问题,可以用三分枚举球心求解。

最小球覆盖:
模拟退火算法:

#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
const int inf = 0x3f3f3f3f;
const double start_T = 1000;
struct point3d
{
    double x,y,z;
}dat[150];
int n;
double dis(point3d a, point3d b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));
}
double solve()
{
    double step=start_T,ans=inf,mt;
    point3d z;
    z.x=z.y=z.z=0;
    int s=0;
    while(step>eps)
    {
        for (int i = 0; i < n; ++i)
        {
            if (dis(z,dat[s])<dis(z,dat[i])) s=i;
        }
        mt=dis(z,dat[s]);
        ans=min(ans,mt);
        z.x+=(dat[s].x-z.x)/start_T*step;
        z.y+=(dat[s].y-z.y)/start_T*step;
        z.z+=(dat[s].z-z.z)/start_T*step;
        step*=0.97;
    }
    return ans;
}
int main()
{
    double ans;
    cin>>n;
    for (int i = 0; i < n; ++i)
        scanf("%lf%lf%lf",&dat[i].x,&dat[i].y,&dat[i].z);
    ans=solve();
    printf("%.8f\n",ans);额
    return 0;
}

Problem K. Kangaroo Puzzle

因为矩阵比较小,而可以进行的操作次数远远大于矩阵大小。所以,每次选定两个1进行合并,因为一个点走,其他的点也走。每次都让选定的第一个点走到第二个点的位置,进行若干次后,一定会成功追及。

还可以采用随机化算法,生成随机数,进行 \(50000\) 次移动。

代码:

#include <bits/stdc++.h>
using namespace std;
char ss[30];
char c[4]={'U','D','L','R'};
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",ss+1);
    for(int i=1;i<=50000;i++)
        printf("%c",c[rand()%4]);
    printf("\n");
    return 0;
}

Problem M. Mediocre String Problem

拓展 KMP+Manacher+差分
对于从 \(s\) 中选出的字符串,可以分成两部分一部分与从 \(t\) 中选出的字符串恰好相反,一部分为回文串。
对于回文串部分,可以用 \(Manacher\)\(O(n)\) 时间内求出每个位置的回文串长度。
对于另一部分,先把 \(s\) 串反向,然后利用拓展KMP,求出反向后的串 \(s^{'}\) 的每个后缀与串 \(t\) 的最长公共前缀,就相当找到了原串 \(s\) 中与 \(t\) 部分相反的串。
然后,就是把这两部分进行组合。
对于回文串,需要求出每个位置可以是几个回文串的起始位置。利用差分解决,从后向前进行,在每个回文串的中心位置 \(+1\),在结束位置的下一个位置 \(-1\),每次累加。
注意下标,一开始代码中 \(num\) 是以 \(1\) 为起始下标的,导致一直 \(WA\)\(-1\) 后就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
char ss[N], t[N], sr[N];
char sn[N << 1];
int sen[N << 1], num[N];
int extend[N], nxt[N];
int init()
{
    int len = strlen(ss), cnt = 1;
    sn[0] = '$';
    sn[1] = '#';
    for (int i = 0; i < len;i++)
    {
        sn[++cnt] = ss[i];
        sn[++cnt] = '#';
    }
    sn[++cnt] = '\0';
    return cnt;
}
void  manacher()
{
    int id, mx = 0;
    int len = init();//扩充到的最后一个位置坐标,而不是扩充后的长度
    //cout << "len=" << len << endl;
    for (int i = 1; i < len;i++)
    {
        sen[i] = mx > i ? min(sen[2 * id - i], mx - i) : 1;
        while(sn[i-sen[i]]==sn[i+sen[i]])
            sen[i]++;
        if(i+sen[i]>mx)
        {
            id = i;
            mx = i + sen[i];
        }
    }
    //差分记录:!!!!!!
    for (int i = len - 2; i >= 2;i--)
    {
        int t = i / 2;
        num[t-1]++;
        num[t - (sen[i] / 2)-1]--;
    }
    for (int i = len / 2 - 1; i >= 1; i--)
        num[i] += num[i + 1];
}

void getNext(char s[])//模式串
{
    int ls=strlen(s),i=0;
    nxt[0]=ls;
    while(i<ls&&s[i]==s[i+1])
        i++;
    nxt[1]=i;
    int k=1;//已知的最大匹配的开始位置
    for(int i=2;i<ls;i++)
    {
        int len=k+nxt[k];//已知的最大匹配的结束位置
        nxt[i]=min(nxt[i-k],max(0,len-i));
        while(i+nxt[i]<ls&&s[nxt[i]]==s[i+nxt[i]])//如果已知的最大匹配不能满足要求,继续判断
            nxt[i]++;
        if(i+nxt[i]>k+nxt[k])//更新已知的最大匹配
            k=i;
    }
}
void getExtend(char sa[],char sb[])//主串,模式串
{//与求next数组的步骤一样,不够是两个不同的字符串之间
    getNext(sb);
    int i=0,la=strlen(sa),lb=strlen(sb);
    while(sa[i]==sb[i]&&i<la&&i<lb)
        i++;
    extend[0]=i;
    int k=0;
    for(int i=1;i<la;i++)
    {
        int len=k+extend[k];
        extend[i]=min(nxt[i-k],max(0,len-i));
        while(i+extend[i]<la&&extend[i]<lb&&sb[extend[i]]==sa[i+extend[i]])//多了一个条件
            extend[i]++;
        if(i+extend[i]>k+extend[k])
            k=i;
    }
}
ll solve(int len)
{//注意extend从0开始存
    ll ans = 0;
    for (int i = 0; i <len-1;i++)
        ans += 1LL*extend[len - i-1] * num[i+1];
    return ans;
}
int main()
{
    scanf("%s", ss);
    scanf("%s", t);
    manacher();
    int len = strlen(ss);
    for (int i = 0; i < len;i++)//先翻转ss串
        sr[len - i - 1] = ss[i];
    getExtend(sr, t);
    printf("%lld\n", solve(len));
    return 0;
}

Problem E. Eva and Euro coins

归约。
以下处理基于一个结论:一个位置的 \(1\) 可以在不跨越 \(1\) 的情况下,向左移动 \(k\) 个位置。
对于连续的 \(k\)\(1\) 可以变换成 \(0\)
那么就可以把连续的 \(k\)\(0\) 或者 \(1\) 消除。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+6;
char s[N],t[N];
int num[N][2];
int n,k;
void solve(char ss[])
{
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        num[++cnt][0]=ss[i]-'0';
        num[cnt][1]=num[cnt-1][0]==ss[i]-'0'?num[cnt-1][1]+1:1;
        if(num[cnt][1]==k)
            cnt-=k;
    }
    for(int i=1;i<=n;i++)
    {
        if(i<=cnt) ss[i]=num[i][0]+'0';
        else ss[i]='0';
    }
    //cout<<ss+1<<endl;
}
int main()
{
    scanf("%d%d",&n,&k);
    scanf("%s",s+1);
    scanf("%s",t+1);
    if(k==1)
    {
        printf("Yes\n");
        return 0;
    }
    solve(s);
    solve(t);
    if(strcmp(s+1,t+1)==0)
        printf("Yes\n");
    else
        printf("No\n");
    return 0;
}

posted @ 2020-05-25 15:26  xzx9  阅读(194)  评论(0编辑  收藏  举报