AtCoder Grand Contest 035

妖精的绣舞 提交于 2020-03-17 06:14:21

Preface

Atcoder的题都好劲啊,都是我做不动的计数构造

就当锻炼自己的思维能力了(基本都是bzt教的)


A - XOR Circle

bzt说这题数据太水了只要判一下所有数异或值是否为\(0\)就能过,但我们要考虑正解(数据太弱我也不知道到底对不对)

首先我们发现放数的时候必然会出现\(a_1\to a_1\operatorname{xor} a_2\to a_2\to a_1\)的情况,即三个数一组的循环

那么我们可以得到一个普遍的结论:只有三种数且每种数字个数相同

然后你直接提交就会WA掉,原因是忽略了\(0\)的情况,因此我们要加上下面两个特判:

  1. 全为\(0\)时显然合法
  2. \(3|n\)且只有两种数(一种是\(0\)),\(0\)的个数为\(\frac{n}{3}\),非零的那个数个数为\(\frac{2n}{3}\)。假设非零数为\(x\),显然可以放置成\(x\to 0\to x\to x\to 0\to x\to \cdots x\to 0\to x\)
#include<cstdio>
#include<cctype>
#include<map>
#include<utility>
#define RI register int
#define CI const int&
#define Tp template <typename T>
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=100005;
int n,x,size; map <int,int> C;
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        char Fin[S],*A,*B;
    public:
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        #undef tc
}F;
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    RI i; for (F.read(n),i=1;i<=n;++i)
    F.read(x),(!C.count(x))&&(++size),++C[x];
    if (size>3) return puts("No"),0; pi a,b,c;
    a=*C.begin(); C.erase(C.begin());
    if (size==1) return puts(a.fi?"No":"Yes"),0;
    if (n%3) return puts("No"),0; b=*C.begin(),C.erase(C.begin());
    if (size==2) return puts(!a.fi&&(a.se==n/3&&b.se==n/3*2)?"Yes":"No"),0;
    c=*C.begin(),C.erase(C.begin());
    if ((a.fi^b.fi)!=c.fi) return puts("No"),0;
    if (a.se!=b.se||a.se!=c.se) puts("No"); else puts("Yes"); return 0;
}

B - Even Degrees

构造题,首先一眼发现\(m\)为奇数必然无解,考虑偶数的构造

我们考虑先找出一棵生成树,然后将所有非树边随便定向

然后考虑对于生成树上的边,如果我们以及处理完了其子树内的所有边,那么我们可以直接根据此时它的出度来决定它与父亲的边的方向

然后这样除了根节点所有节点出度都为偶数,然后总边数也是偶数,因此根节点出度必为偶数,于是得到了一种构造方案

#include<cstdio>
#include<cctype>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=100005;
struct edge
{
    int to,nxt,id;
}e[N<<1]; int head[N],n,cnt,m,x[N],y[N],out[N],ansx[N],ansy[N],anc[N]; bool vis[N],cs[N];
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        #define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
        char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[15];
    public:
        FileInputOutput() { Ftop=Fout; Fend=Fout+S; }
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        Tp inline void write(T x,const char& ch)
        {
            RI ptop=0; while (pt[++ptop]=x%10,x/=10);
            while (ptop) pc(pt[ptop--]+48); pc(ch);
        }
        inline void flush(void)
        {
            fwrite(Fout,1,Ftop-Fout,stdout);
        }
        #undef tc
        #undef pc
}F;
inline void addedge(CI x,CI y,CI z)
{
    e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
    e[++cnt]=(edge){x,head[y],z}; head[y]=cnt;
}
#define to e[i].to
inline void DFS1(CI now=1)
{
    vis[now]=1; for (RI i=head[now];i;i=e[i].nxt)
    if (!vis[to]) cs[e[i].id]=1,anc[to]=e[i].id,DFS1(to);
}
inline void DFS2(CI now=1,CI fa=0)
{
    for (RI i=head[now];i;i=e[i].nxt) if (cs[e[i].id]&&to!=fa) DFS2(to,now);
    if (out[now]&1) ansx[anc[now]]=now,ansy[anc[now]]=fa;
    else ansx[anc[now]]=fa,ansy[anc[now]]=now,++out[fa];
}
#undef to
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    RI i; for (F.read(n),F.read(m),i=1;i<=m;++i)
    F.read(x[i]),F.read(y[i]),addedge(x[i],y[i],i);
    if (m&1) return puts("-1"),0;
    for (DFS1(),i=1;i<=m;++i) if (!cs[i])
    ++out[x[i]],ansx[i]=x[i],ansy[i]=y[i];
    for (DFS2(),i=1;i<=m;++i) F.write(ansx[i],' '),F.write(ansy[i],'\n');
    return F.flush(),0;
}

C - Skolem XOR Tree

又是构造题,先把无解判了,发现若\(n\)\(2\)的幂次必然无解(小于它的数这一位上不可能为\(1\)

否则我们可以发现现在的\(n\ge 3\),而\(n=3\)的构造方案样例已经告诉我们了,我们考虑在上面扩展

每次我们按序添加两个点\(i,i+1\),其中\(i\)为偶数,那么我们显然可以构造一条\((i,i+1)-(1,i)-(1,n+i+1)-(n+i+1,n+i)\)的链,显然这样链上就满足了条件

因此若\(n\)为奇数就做完了,然后若\(n\)为偶数只可能剩下最后一个点没选,相当于找一条树上路径异或和为\(n\),直接前缀和处理一下即可

#include<cstdio>
#include<vector>
#include<cstdlib>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,cur,pfx[N<<1]; vector <int> v[N<<1]; map <int,int> Hash;
inline void addedge(CI x,CI y)
{
    printf("%d %d\n",x,y); v[x].push_back(y); v[y].push_back(x);
}
inline void DFS(CI now=1,CI fa=0)
{
    pfx[now]=pfx[fa]^(now<=n?now:now-n);
    if (pfx[now]==n) addedge(1,n),addedge(now,n<<1),exit(0);
    if (Hash.count(pfx[now]^n^1))
    addedge(Hash[pfx[now]^n^1],n),addedge(now,n<<1),exit(0);
    Hash[pfx[now]]=now; for (vector <int>::iterator it=v[now].begin();it!=v[now].end();++it)
    if (*it!=fa) DFS(*it,now);
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    scanf("%d",&n); for (cur=1;cur<n;cur<<=1);
    if (cur==n) return puts("No"),0; puts("Yes");
    addedge(1,2); addedge(2,3); addedge(3,n+1); addedge(n+1,n+2); addedge(n+2,n+3);
    for (RI i=5;i<=n;i+=2) addedge(1,i-1),addedge(i,i-1),addedge(1,n+i),addedge(n+i,n+i-1);
    if (n&1) return 0; return DFS(),0;
}

D - Add and Remove

思路很妙,代码很短的一题

首先看到数据范围一眼DP,但是发现状态根本不好表示

因此考虑倒序处理这个过程,每次相当于从两边向中间吐出一个数,这样我们就可以求出每个数对答案的贡献\(c_i\)然后乘上位置上的值即可

考虑如果有很多数,它们的贡献\(c_i\)已知,那么如果我们将\(i\)\(i+1\)间的数吐出来,那么这个数对答案的贡献就是\(c_i+c_{i+1}\)

因此我们可以直接搞一个DP,定义\(f_{l,r,cl,cr}\)表示区间\((l,r)\),左端点的贡献是\(cl\),右端点的贡献是\(cr\),考虑转移:

\[f_{l,r,cl,cr}=\min_{m=l+1}^{r-1} f_{l,m,cl,cl+cr}+f_{m,r,cl+cr,cr}+(cl+cr)*a[m]\]

那么最后的答案就是\(f_{1,n,1,1}+a_1+a_n\),然后由于一些奇怪的东西这个的状态数是\(O(2^n\times Poly(n))\)的,因此直接爆搜就好了

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=20;
int n,a[N];
inline long long DFS(CI l,CI r,CI cl,CI cr)
{
    if (r-l+1<=2) return 0; long long ret=1e18;
    for (RI i=l+1;i<r;++i) ret=min(ret,DFS(l,i,cl,cl+cr)+DFS(i,r,cl+cr,cr)+1LL*(cl+cr)*a[i]);
    return ret;
}
signed main()
{
    RI i; for (scanf("%d",&n),i=1;i<=n;++i)
    scanf("%d",&a[i]); return printf("%lld",DFS(1,n,1,1)+a[1]+a[n]),0;
}

E - Develop

神仙计数题,被神仙bzt出在了模拟赛里

首先考虑将原问题转化,我们可以发现对于一个状态,可以将每一个数看做一个点,那么如果\(x\)在集合内就向\(x-2\)\(x+k\)(如果存在)连一条有向边,如果最后得出的图无环即有解

那么我们分\(k\)的奇偶性讨论,首先如果\(k\)是偶数我们可以将原问题的奇偶数分别考虑最后乘起来

那么现在问题等价于选一个数\(x\)就不能选\(x-1\)\(x+\frac{k}{2}\)

发现其实这就等价于不能有连续\(\frac{k}{2}+1\)\(1\)被选,直接令\(f_{i,j}\)表示第\(i\)位结尾有\(j\)个连续的\(1\)再DP即可

考虑\(k\)为奇数的情况,我们发现一个环必然是\(a\to a-2\to a-4\to \cdots a-2d+k\to a-2(d+1)+k\to \cdots \to a-2(k-1)+2k\to a\)

即先减去一些\(2\)然后加上一个\(k\)再减去一些\(2\)再加上一个\(k\)

那么分析一下就是选出\(k\)\(-2\)\(2\)\(k\),转化一下就变成选出\(k\)\(2\)\(2\)\(-k\)

我们考虑一种神奇的建模,将奇偶两排点建在左右两边,然后将两排数对齐使得左边的奇数+\(k\)=右边的偶数

然后我们发现一条不合法的路径(即出现环)就是这张图上长度大于等于\(k+2\)的路径(多画画图理解下)

因此我们定义DP状态\(f_{i,j,k}\)表示做到第\(i\)行,一直向下走的路径长为\(j\),走成下-左-下的路径长为\(k\),那么所有\(k\le k+1\)的路径都是合法的,直接转移即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=155;
int n,k,mod;
inline void inc(int& x,CI y)
{
    if ((x+=y)>=mod) x-=mod;
}
namespace Case1 //Even number solver
{
    int f[N][N];
    inline int calc(CI n,CI k)
    {
        RI i,j; for (f[0][0]=i=1;i<=n;++i) for (f[i][0]=f[i-1][0],j=1;j<=k;++j)
        inc(f[i][0],f[i-1][j]),f[i][j]=f[i-1][j-1];
        int ret=0; for (i=0;i<=k;++i) inc(ret,f[n][i]); return ret;
    }
    inline void solve(void)
    {
        printf("%d",1LL*calc(n>>1,k>>1)*calc(n-(n>>1),k>>1)%mod);
    }
};
namespace Case2 //Odd number solver
{
    int f[N][N][N],ans;
    inline void solve(void)
    {
        RI i,j,p; for (f[0][0][0]=i=1;i<=n;++i)
        {
            int r=i<<1,l=r-k,fl=1<=l&&l<=n,fr=1<=r&&r<=n;
            for (j=0;j<=n;++j) for (p=0;p<=k+1;++p)
            {
                inc(f[i][0][0],f[i-1][j][p]); if (fr) inc(f[i][j+1][0],f[i-1][j][p]);
                if (fl&&p+1<k+2) inc(f[i][0][p?p+1:p],f[i-1][j][p]);
                if (fl&&fr&&max(j+2,p+1)<k+2) inc(f[i][j+1][max(j+2,p+1)],f[i-1][j][p]);
            }
        }
        for (i=0;i<=n;++i) for (j=0;j<=k+1;++j)
        inc(ans,f[n][i][j]); printf("%d",ans);
    }
};
int main()
{
    scanf("%d%d%d",&n,&k,&mod); if (k&1) Case2::solve();
    else Case1::solve(); return 0;
}

F - Two Histograms

妙不可言的一题,首先我们发现一些不同的操作序列\(k,l\)可能会构造出相同的矩阵,那么我们考虑将一些能构造出相同矩阵的序列用一种方法表示出来就不会计重了

考虑我们可以尽量把一种方案中的\(l\)中的一个数从\(l\)移动到\(k\)中,推导一下发现就是要满足\(k_i+1=j\)\(l_j=i\)的点对\((i,j)\)

因此我们发现这样每个\(i\)只会和一个\(j\)唯一配对,因此我们可以直接计算出大于等于\(i\)对配对的方案数

那么我们枚举点对数目\(i\)容斥计算即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005,mod=998244353;
int n,m,d,ans,fact[N],inv[N],pwn[N],pwm[N];
inline void inc(int& x,CI y)
{
    if ((x+=y)>=mod) x-=mod;
}
inline void dec(int& x,CI y)
{
    if ((x-=y)<0) x+=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n,CI m)
{
    RI i; for (fact[0]=i=1;i<=m;++i) fact[i]=1LL*fact[i-1]*i%mod;
    for (inv[m]=quick_pow(fact[m]),i=m-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
    for (pwn[0]=i=1;i<=m;++i) pwn[i]=1LL*pwn[i-1]*(n+1)%mod;
    for (pwm[0]=i=1;i<=m;++i) pwm[i]=1LL*pwm[i-1]*(m+1)%mod;
}
inline int C(CI n,CI m)
{
    return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
inline int A(CI n,CI m)
{
    return 1LL*fact[n]*inv[n-m]%mod;
}
int main()
{
    scanf("%d%d",&n,&m); if (n>m) swap(n,m);
    init(n,m); for (RI i=0;i<=n;++i)
    {
        int d=1LL*C(n,i)*A(m,i)%mod*pwm[n-i]%mod*pwn[m-i]%mod;
        if (i&1) dec(ans,d); else inc(ans,d);
    }
    return printf("%d",ans),0;
}

Postscript

感觉AGC好考验思维啊,我可能需要多做一点这样的题目QAQ

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!