2020 CCPC Wannafly Winter Camp Day6 部分题解(C,F~N)

题目链接


C 酒馆战棋

题意:

你有n个随从,用一个长度为n的01串表示,为1则代表有剧毒属性,对方有a个普通随从,b个圣盾随从,c个嘲讽随从,d个圣盾嘲讽随从。
你的随从必须从左到右行动,每个随从可以选择一个对方随从进行一次攻击,规则如下:
1、若对方仍存活带嘲讽属性随从则必须优先攻击嘲讽属性随从
2、若对方被攻击的随从带圣盾,不论你的随从是否带剧毒,都可以使其圣盾属性去除,但不至死
3、你的剧毒随从的攻击才能消灭对方的不带圣盾的随从
问最多和最少情况可以消灭对方多少随从
题目细节还是看题吧(懒得写了

解题思路:

贪心乱搞一下,最优时显然应该先用普通随从去打掉对面随从的圣盾,最差情况应该用剧毒随从撞圣盾,让普通随从白给

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 105
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

int T, n, a[2], b[2], c[2], d[2];
string s;
int minans, maxans;

void solve1x()
{
    if (c[1]) {c[1]--; maxans++; return ;}
    if (d[1]) {d[1]--; c[1]++; return ;}
    if (a[1]) {a[1]--; maxans++; return ;}
    if (b[1]) {b[1]--; a[1]++; return ;}
}
void solve1n()
{
    if (d[0]) {d[0]--; c[0]++; return ;}
    if (c[0]) {c[0]--; minans++; return ;}
    if (b[0]) {b[0]--; a[0]++; return ;}
    if (a[0]) {a[0]--; minans++; return ;}
}
void solve0x()
{
    if (d[1]) {d[1]--; c[1]++; return ;}
    if (c[1]) {return ;}
    if (b[1]) {b[1]--; a[1]++; return ;}
    if (a[1]) {return ;}
}
void solve0n()
{
    if (c[0]) {return ;}
    if (d[0]) {d[0]--; c[0]++; return ;}
    if (a[0]) {return ;}
    if (b[0]) {b[0]--; a[0]++; return ;}
}
int main()
{
    cin>>T;
    while (T--)
    {
        maxans=minans=0;
        cin>>n>>a[0]>>b[0]>>c[0]>>d[0];
        a[1]=a[0], b[1]=b[0], c[1]=c[0], d[1]=d[0];
        cin>>s;
        for (int i=0; i<n; i++)
        {
            if (s[i]=='1') solve1x(), solve1n();
            else solve0x(), solve0n();
        }
        cout<<maxans<<" "<<minans<<"\n"; 
    }
    
}


F 图与三角形

题意:

给一个n个点的完全图,边按照给定的奇怪的公式染成黑色或白色。然后问图中有几个(i, j, k)满足三个点之间的边同色。

解题思路:

可以记录每个点有几个黑边和几个白边,然后计算有几对边都为黑,计入答案(不管那一对黑边连接的点之间的边是黑的还是白的),白边对数也计入答案。这时候对于所有 黑黑黑 三角形,被计入了三次,白白白 计入三次,黑白白 计入一次,黑黑白 计入一次。因此只要在答案里减去每个点 一黑一白的边对的对数除以2,结果再除以3即可。

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 5010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

ll n, white[maxn], black[maxn];
ll a, b, c, p, d, ansb, answ, ansbw;

int check(ll x, ll y)
{
    if ((a*(x+y)*(x+y)+b*(x-y)*(x-y)+c)%p>d) return 1;
    return 0;
}

int main()
{
    cin>>n>>a>>b>>c>>p>>d;
    for (ll i=1; i<=n; i++)
    {
        for (ll j=i+1; j<=n; j++)
        {
            if (check(i, j)) black[i]++, black[j]++;
            else white[i]++, white[j]++;
        }
    }
    for (int i=1; i<=n; i++)
    {
        ansb+=black[i]*(black[i]-1)/2;
        answ+=white[i]*(white[i]-1)/2;
        ansbw+=black[i]*white[i];
    }
    cout<<(ansb+answ-ansbw/2)/3;
}


G 单调栈

题意:

对于一个1到n的排列,定义f[i] :假设前i-1个数中比第i个数小的最大的数的位置为j,则f[i] = f[j] +1,且f[1]=1。限给定残缺的 f[1]到f[n](残缺位置用-1表示,要求算出符合给定f的字典序最小的排列。

解题思路:

要求字典序最小,因此我们就要让排在前面的数字尽量少。
先考虑第一个数,f[1]肯定等于1(就算这个位置是空缺的也一定为1)。要让第一个数比较小,也就是要让后面比它小的数尽量少一些,而比第一个小的数的f[]都为1。因此,假设有k个f[]为1的位置,只要从后往前给f[]为一的位置的答案分别赋为1~k即可使第一个位置最小。
处理完第一个数后,我们可以把f[]为1的数从排列中去掉,然后再让所有f[]减去1,之后再保证当前排列的第一个位置最小,进行同样操作递归下去即可。
讲的貌似不是很清楚,可以参考代码。理解了题目的规律后应该使不难理解做法的。

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 5010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

int T, n, f[maxn], ans[maxn];

int main()
{
    cin>>T;
    while (T--)
    {
        cin>>n;
        for (int i=1; i<=n; i++) cin>>f[i];
        int now=0, buf;
        for (int i=1; i<=n; i++)
        {
            buf=0;
            for (int j=1; j<=n; j++)
            {
                if (f[j]==-1) f[j]=1;
                if (f[j]==1) break;
            }
            for (int j=1; j<=n; j++)
               if (f[j]==1) buf++;
            if (buf==0) break;
            buf=now+buf;
            now=buf;
            for (int j=1; j<=n; j++)
                if (f[j]==1) ans[j]=buf--;
            for (int j=1; j<=n; j++)
                if (f[j]>0) f[j]--;
        }
        for (int i=1; i<=n; i++)
            if (f[i]<0) ans[i]=++now;
        for (int i=1; i<=n; i++)
            cout<<ans[i]<<" ";
        cout<<"\n";
    }
    return 0;
}


H 异或询问

题意:

给定a1…an,定义f(x)为有几个ai小于等于x。
q次询问,每次给l r x,询问f(l^x)2…f(r^x)2的和为多少

解题思路:

对于一个集合,其二进制下元素表示为
{0, 1, 10, 11, 100, … 111111}
对其中每个元素异或一个x(x小于等于二进制111111)后形成一个新的集合,集合仍然表示为
{0,1, 10, 11, 100, … 111111}
发现上述规律后对l到r的询问就可以拆成对不超多log(n)段的连续区间求其f(x)2
对一段连续的区间求答案可以用前缀和乱搞一下,然后每次二分找到相应位置即可。

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 201000
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

ll n, q, a[maxn]={-1}, cot[maxn], sum[maxn];

ll cal(ll x)
{
    ll l=0, r=n, mid;
    while (l<r)
    {
        mid=(l+r)/2;
        if (a[mid]>x) r=mid;
        else l=mid+1;
    }
    if (a[l]>x) l--;
    if (l<=0) return 0;
    return (sum[l]+(x-a[l]+1)*cot[l]%mod*cot[l]%mod)%mod;
}

ll sol(ll k, ll x)
{
    if (k<0) return 0;
    ll l=0, r=0, res=0;
    for (ll p=30; p>=0; p--)
    {
        l^=(x&(1<<p));
        if (k&(1<<p))
        {
            r=l+(1<<p)-1;
            res=(res+cal(r)-cal(l-1)+mod)%mod;
        }
        l^=(x&(1<<p));
        l|=(x&(1<<p))^(k&(1<<p));
    }
    return (res+cal(l)-cal(l-1)+mod)%mod;
}

int main()
{
    cin>>n>>q;
    for (ll i=1; i<=n; i++)
        cin>>a[i];
    map<ll, ll> mp;
    for (ll i=1; i<=n; i++)
        mp[a[i]]++;
    n=0;
    for (auto &i: mp)
    {
        a[++n]=i.first;
        cot[n]=i.second;
    }
    for (ll i=1; i<=n; i++)
        cot[i]=cot[i-1]+cot[i];
    for (ll i=1; i<=n; i++)
        sum[i]=(sum[i-1]+(a[i]-a[i-1])*cot[i-1]%mod*cot[i-1]%mod)%mod;
    for (ll qq=1, l, r, x; qq<=q; qq++)
    {
        cin>>l>>r>>x;
        cout<<(sol(r, x)-sol(l-1, x)+mod)%mod<<"\n";
    }
    return 0;
}


I 变大!

题意:

给定a1…an,你可以进行k次操作,每次可以选定一个ai,使它和它前一个数字以及它后一个数字变成三者中最大的数。
分别对k=1…n输出最优操作后序列的和

解题思路:

(开始看到ai的范围很小还以为dp肯定和值有关,没想到其实是坑
每次操作影响三个值,多次操作影响的值若可以连起来就可以形成一段,可以观察出,这多次操作若采取最优操作,连起来的这一段中的每个值都可以变成原本序列中这一段里的最大值。
因此可以背包dp,dp[i][j]表示进行了i次操作,枚举到第j位。枚举末端在j位的一段用了几次操作,转移即可。

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 5010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

int T, n, num[55], mx[55][55];
int dp[55][55], ans[55];

int main()
{
    cin>>T;
    while (T--)
    {
        cin>>n;
        for (int i=1; i<=n; i++)
            cin>>num[i];
        for (int i=1; i<=n; i++)
        {
            mx[i][i]=num[i];
            for (int j=i+1; j<=n; j++)
                mx[i][j]=max(mx[i][j-1], num[j]);
        }
        memset(dp, 0, sizeof(dp));
        for (int i=1; i<=n; i++)
            dp[0][i]=dp[0][i-1]+num[i];
        for (int k=1; k<=(n-1)/2; k++)
        {
            for (int i=1; i<=n; i++)
            {
                dp[k][i]=dp[k][i-1]+num[i];
                for (int l=3; l<=i && l/2<=k; l++)
                    dp[k][i]=max(dp[k][i], dp[k-l/2][i-l]+mx[i-l+1][i]*l);
            }
        }
        for (int k=1; k<=(n-1)/2; k++)
            cout<<dp[k][n]<<" ";
        for (int i=(n-1)/2+1; i<=n; i++)
            cout<<mx[1][n]*n<<" ";
        cout<<"\n";
    }
    return 0;
}


J K重排列

题意:

感觉不好描述,形式化一下直接抄题面好了
对于一个排列 p[1…n],我们设 pk[i]=p[pk-1[i]],且 p1[i]=p[i]。
如果存在一个 K,使得对于所有的 i 都有 pK[i]=i,那么 K 就是 p 的一个周期。
给定 n,K,你需要计算有几个 1…n 的排列,满足 K 是它的一个周期

解题思路:

这个规则其实就相当于排列上的某几个数形成了环
例如: 312
第一个位置是3 --> 第三个位置是2 --> 第二个位置是1
即形成了一个长度为3的环。
一个1到n的排列中的每个数肯定都处于一个环中(如果像123这样的类型中的数就是自环)
题目中的周期为K可以理解为,对于每个数,走K步后都回到原来的位置,也就是要求序列中的每个数字都是K的约数。
因此只要枚举出K的约数,然后大力dfs算就行了。
有个要特别注意的点,算数目的时候去重要特别注意一下,比如dfs枚举有k个长度为i的环,要多除以k!
还有一个就是对于一个长度为k的环(数字已确定),其环内部排列方式有 (k-1)!种。

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 2010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

ll T, n, k, jc[maxn]={1};
ll c[55][55], inv[maxn], yz[maxn], ans;
ll ksm(ll x, ll k)
{
    ll res=1;
    while (k)
    {
        if (k&1) res=res*x%mod;
        k/=2, x=x*x%mod;
    }
    return res;
}

void dfs(ll k, ll s, ll res)
{
    if (s>n || k>yz[0]) return ;
    dfs(k+1, s, res);
    ll buf=1;
    for (ll i=1; s+i*yz[k]<=n; i++)
    {
        buf=buf*c[yz[k]][n-(s+(i-1)*yz[k])]%mod*jc[yz[k]-1]%mod;
        dfs(k+1, s+i*yz[k], res*buf%mod*inv[i]%mod);
        ans=(ans+res*buf%mod*inv[i]%mod)%mod;
    }
    return ;
}

int main()
{
    cin>>T;
    for (int i=0; i<=50; i++)
        c[0][i]=1;
    for (int i=1; i<=50; i++)
        for (int j=1; j<=i; j++)
            c[j][i]=(c[j][i-1]+c[j-1][i-1])%mod;
    for (ll i=1; i<=50; i++)
        jc[i]=jc[i-1]*i%mod, inv[i]=ksm(jc[i], mod-2);
    while(T--)
    {
        cin>>n>>k;
        yz[0]=ans=0;
        for (ll i=2; i<=n; i++)
            if (k%i==0) yz[++yz[0]]=i;
        dfs(1, 0, 1);
        cout<<(ans+1)%mod<<"\n";
    }
    return 0;
}


K 最大权值排列

题意:

签到题,乱搞搞

解题思路:

易知,在中间的数对答案贡献次数更多,因此尽量把大的往中间放,同时要求字典序最小
那答案自然就是 1,3,5,7…8,6,4,2了

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1000010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

int n, i;

int main()
{
    cin>>n;
    for (i=1; i<=n; i+=2)
        cout<<i<<" ";
    i=n;
    if (n&1) i--;
    for (; i>=2; i-=2)
        cout<<i<<" ";
    return 0;
}


L 你吓到我的马了.jpg

题意:

签到题,乱搞搞

解题思路:

让人回味当年初学bfs感觉的题
水题题解真好水啊.jpg

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 105
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

int n, m, obs[maxn][maxn], ans[maxn][maxn];
char x;
int fx[4][2]={-1, 0, 1, 0, 0, -1, 0, 1};
int nxt[4][2][2]={-2, -1, -2, 1, 2, -1, 2, 1, -1, -2, 1, -2, -1, 2, 1, 2};
int si, sj;
int check(int x, int y)
{
    if (x<1 || x>n || y<1 || y>m || obs[x][y]) return 0;
    if (ans[x][y]<inf) return -1;
    return 1;
}
int main()
{
    cin>>n>>m;
    for (int i=1; i<=n; i++)
        for (int j=1; j<=m; j++)
        {
            ans[i][j]=inf;
            cin>>x;
            if (x=='X') obs[i][j]=1;
            if (x=='M') si=i, sj=j;
        }
    queue<pair<int, int> > que;
    que.push(make_pair(si, sj));
    ans[si][sj]=0;
    while (!que.empty())
    {
        int nx=que.front().first, ny=que.front().second;
        que.pop();
        for (int i=0; i<4; i++)
        {
            if (check(nx+fx[i][0], ny+fx[i][1])==0) continue;
            for (int j=0; j<2; j++)
            {
                if (check(nx+nxt[i][j][0], ny+nxt[i][j][1])<=0) continue;
                ans[nx+nxt[i][j][0]][ny+nxt[i][j][1]]=ans[nx][ny]+1;
                que.push(make_pair(nx+nxt[i][j][0], ny+nxt[i][j][1]));
            }
        }
    }
    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
        {
            if (ans[i][j]==inf) cout<<-1<<" ";
            else cout<<ans[i][j]<<" ";
        }cout<<"\n";
    }
    return 0;
}


M 自闭

题意:

签到题,乱搞搞

解题思路:

按照题意模拟就好了

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 105
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

int n, m, w;
int ac[110][15], maxwa[110][15], contiwa[110][15];
int SUB[110], AC[110], ACcot[15];

int main()
{
    cin>>n>>m>>w;
    for (int i=1, x, y, z; i<=w; i++)
    {
        cin>>x>>y>>z;
        SUB[x]++;
        if (z==1)
        {
            if (ac[x][y]==0) ACcot[y]++, AC[x]++;
            ac[x][y]=1;
            contiwa[x][y]=0;
        }
        else
        {
            contiwa[x][y]++;
            maxwa[x][y]=max(maxwa[x][y], contiwa[x][y]);
        }
    }
    for (int i=1; i<=n; i++)
    {
        if (SUB[i]==0)
        {
            cout<<998244353<<"\n";
            continue;
        }
        if (AC[i]==0)
        {
            cout<<1000000<<"\n";
            continue;
        }
        if (AC[i]==m)
        {
            cout<<0<<"\n";
            continue;
        }
        int res=0;
        for (int j=1; j<=m; j++)
        {
            if (ACcot[j] && (!ac[i][j])) res+=20;
            if (ACcot[j]>=n/2 && (!ac[i][j])) res+=10;
            res+=maxwa[i][j]*maxwa[i][j];
            if (!ac[i][j]) res+=maxwa[i][j]*maxwa[i][j];
        }
        cout<<res<<"\n";
    }
    return 0;
}


N 合并!

题意:

签到题,乱搞搞

解题思路:

观察后可发现不论怎么枚举每个数字都和其它数字做积对答案贡献了一次,所以枚举即可。
(开始还想了个憨批贪心,试了下过了还挺高兴

#pragma GCC optimize(3)
#include 
#define inf 1000000000
#define Inf 223372036854775807
#define maxn 1000010
using namespace std;
typedef long long ll;
const ll mod=998244353;
const long double eps=1e-9;
const long double PI=acos(-1.0);

ll n, num[maxn], ans;

int main()
{
    cin>>n;
    for (int i=1; i<=n; i++) cin>>num[i];
    for (int i=1; i<=n; i++)
        for (int j=i+1; j<=n; j++)
            ans+=num[i]*num[j];
    cout<<ans;
}

你可能感兴趣的:(2020 CCPC Wannafly Winter Camp Day6 部分题解(C,F~N))