加载中…

返回上一页

CSP-S模拟11

下发文件和题解

A. 回文(512MB 1100ms)

可以发现数据范围大小只有 500 × 500,那么考虑暴力 dp,从两头更新状态到中间点.
dp[i][j][k][l] 表示左上为 (i,j) 的点与右下为 (k,l) 的点,当前已经配对出了多少种可能的回文情况. 显然这两个点的字母相等时才可以进行转移.

空间开不下. 怎么办?

发现如果要想构成回文,左边走的步数必须等于右边走的步数. 所以只需要枚举右下角的行(或列),根据左上角点已走的步数计算出其另一个坐标.

统计答案时只需要枚举每一个中间点(从 1,1 走到 n,m 走的步数是偶数的话就是每一个中间的前面一个点),累加每个中间点的答案即可.

注意模数不是 998244353因为这个我去调 dp 调了半个多小时.

点击查看代码
#include<bits/stdc++.h>
#define ll int
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 601
#define mod 993244853// 就因为这个模数,我调了半个多小时,以后一定要认真读题啊啊啊啊啊啊啊!!!!!!
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();
	while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
	return f?-x:x;
}
sinline void write(rll x)
{
	if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0');
}
ll n,m,mid,d,kk,ans;
ll dp[maxn][maxn][maxn];
char mp[maxn][maxn];
int main()
{
	n=read();m=read();mid=(n+m-1>>1)+(n+m-1&1);d=(n+m-1>>1);for(rll i=1;i<=n;i++) scanf("%s",mp[i]+1);if(mp[1][1]^mp[n][m]) { putchar('0'); return 0; }
	dp[1][1][m]=1;
	for(rll i=1;i<=n;i++)
		for(rll j=1;j<=m;j++)
			for(rll k=m,l;k>=j;k--)
			{
				if((l=n-(i+j-2-(m-k)))>n||l<=0) continue;
				if(!(mp[i+1][j]^mp[l-1][k])) dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%mod;
				if(!(mp[i+1][j]^mp[l][k-1])) dp[i+1][j][k-1]=(dp[i+1][j][k-1]+dp[i][j][k])%mod;
				if(!(mp[i][j+1]^mp[l-1][k])) dp[i][j+1][k]=(dp[i][j+1][k]+dp[i][j][k])%mod;
				if(!(mp[i][j+1]^mp[l][k-1])) dp[i][j+1][k-1]=(dp[i][j+1][k-1]+dp[i][j][k])%mod;
			}
	for(rll i=1;i<=n;i++)
		for(rll j=1;j<=m;j++)
			for(rll k=m,l;k>=j;k--)
			{
				if((l=n-(i+j-2-(m-k)))>n||l<=0) continue;
				if(i==l&&j==k) ans=(ans+dp[i][j][k])%mod;
				if(i==l&&j==k-1) ans=(ans+dp[i][j][k])%mod;
				if(i==l-1&&j==k) ans=(ans+dp[i][j][k])%mod;
			}
	write(ans);
	return 0;
}

B. 快速排序(128MB 1000ms)

仔细观察这个代码,不难发现他的排序是一个稳定排序,即每一个相同大小数在原来序列中的相对位置与在新序列中的相对位置不变.

怎样才能使相对位置不变呢?

直接按照比较函数 sort 一定不行. 发现每一个 nan 数只会且一定会停留在它前面的最大数的后面.

因此用一个队列记录一下每一个 nan 数前面的最大值以及这个值第几次出现的,sort 原数组,输出时插到对应的位置即可.

我打的是手写队列,是因为赛时害怕 TLE,因为即使这样大样例都跑了 4 秒多. 但是交到 oj 上最慢的也只跑了 0.1s.

点击查看代码
#include<bits/stdc++.h>
#define ll int
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 500001
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='n') { getchar();getchar(); return -1; }
		f|=(ch=='-'),ch=getchar();
	}
	while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
	return f?-x:x;
}
sinline void write(rll x)
{
	if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0');
}
ll t,n,mx,num,tot,l;
ll a[maxn];
ll q1[maxn],q2[maxn];
ll cnt;
int main()
{
	t=read();while(t--)
	{
		cnt=0;mx=num=tot=0;n=read();for(rll i=1;i<=n;i++)
		{
			a[i]=read();
			if(!(~a[i]))
				q1[++cnt]=mx,q2[cnt]=num;
			else
			{
				if(mx<a[i]) mx=a[i],num=1;
				else if(mx==a[i]) num++;
				a[++tot]=a[i];
			}
		}
		l=1;n=tot;sort(a+1,a+n+1);
		while(l<=cnt&&(!q1[l])) putchar('n'),putchar('a'),putchar('n'),put_,l++;
		for(rll i=1;i<=tot;i++)
		{
			if(a[i]!=a[i-1]) num=1;else num++;write(a[i]),put_;
			while(l<=cnt&&q1[l]==a[i]&&q2[l]==num) putchar('n'),putchar('a'),putchar('n'),put_,l++;
		}
		putn;
	}
	return 0;
}

C. 混乱邪恶(512MB 1000ms)

构造.

首先如果总和是奇数,一定不合法(因为其中一定有奇数个奇数,无法消去).

n 设为偶数,如果 n 是奇数就加入一个 0.

然后把 a 数组从小到大排序,构造一个 d 数组,让 a 数组中的每一偶数项减去每一奇数项(即 di = a2i − a2i−1).

d 数组的总和大于等于 n 的时候,一定无解,否则一定有解(下方引用部分有证明).

不断地把 d 中的最大值减去最小值,直到其中元素值均为 1 或仅剩 1 个元素. 每次减的时候把原来 a 数组对应的值的状态取反,这个可以用一个 set队列实现.

下面是这个做法的证明:

可以发现 .

我们尝试对于每个 di 分配一个 ei 使得 ,这样便可以构造出一组满足条件的 ci.

我们不难归纳得出,若 n 个正整数 d1, d2, . . . , dn 的和为偶数且小于 2n,则必存在一种方案:

  • n = 1 显然成立.

  • 对于 n = k ,若 di = 1 显然成立.

  • 若存在 di > 1 我们考虑将 d 的最大值 max d 和最小值 min d 删除,并加入 max d − min d.

  • 不难发现总和减少了 2 min d 即至少 2,且最小值仍然非零,问题归约到 n′ = n − 1 的情况

因此我们证明了一定存在合法的构造方案,并能成功给出一种构造.

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 1000001
#define NP puts("NP-Hard solved")
#define Ch puts("Chaotic evil")
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();
	while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
	return f?-x:x;
}
sinline void write(rll x)
{
	if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0');
}
struct node
{
	ll a,id;
	inline friend bool operator<(rg node a,rg node b) { return a.a<b.a; }
}a[maxn];
ll n,nn,m,sum,tot;
queue<ll> q;
bool ans[maxn],kk;
struct nd
{
	int d,id,idf,idz;bool fl;
	inline friend bool operator < (rg nd x,rg nd y) { return x.d<y.d; }
}d[maxn<<1];
multiset<nd> s;
multiset<nd>::iterator mx,mn,t;
int main()
{
	nn=n=read();m=read();for(rll i=1;i<=n;i++) a[i]=(node){read(),i};
	if(n&1) a[n].id=++n;sort(a+1,a+n+1);
	for(rll i=1,num,id;i<=n;i+=2)
		s.insert(d[(i>>1)+1]=(nd){a[i+1].a-a[i].a,(i>>1)+1,a[i].a,a[i+1].a,0}),sum+=a[i+1].a-a[i].a;
	if(sum&1||sum>=n) { Ch;return 0; } tot=n>>1;
	while(s.size()>1)
	{
		mx=--s.end();mn=s.begin();
		if(mx==mn) { q.push((*mx).id);break; }
		if((*mx).d==1)
		{
			for(t=s.begin();t!=s.end();t++)
			{
				if(kk) d[(*t).id].fl=1;
				q.push((*t).id);kk^=1;
			}
			kk=0;break;
		}
		mn++;
		d[tot]=(nd){(*mx).d-(*mn).d,++tot,(*mn).id,(*mx).id,0};
		s.erase(mx);s.erase(mn);s.insert(d[tot]);
	}
	while(!q.empty())
	{
		rll x=q.front();q.pop();
		if(x<=n)
		{
			if(d[x].fl) ans[d[x].idz]=1;
			else ans[d[x].idf]=1;
		}
		else
		{
			q.push(d[x].idz);q.push(d[x].idf);
			if(d[x].fl) d[d[x].idz].fl=1;
			else d[d[x].idf].fl=1;
		}
	}
	NP;for(rll i=1;i<=nn;i++) write(ans[i]?1:-1),put_;
	return 0;
}

两种玄学做法:

  1. 贪心,选中最大的,直至不能再选. 然后选最小的. 这种方法无法证明正确性(据说能卡掉).
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define sinline static inline
#define rll rg ll
#define maxn 1000002
#define NP puts("NP-Hard solved")
#define Ch puts("Chaotic evil")
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
sinline ll read()
{
	rll f=0,x=0;rg char ch=getchar();
	while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar(); while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
	return f?-x:x;
}
sinline void write(rll x)
{
	if(x<0) putchar('-'),x=-x; if(x>9) write(x/10);putchar(x%10|'0');
}
struct node
{
	ll a,id;
	inline friend bool operator<(rg node a,rg node b) { return a.a<b.a; }
}a[maxn];
ll nn,n,m,sum,tot,id,id2;
struct nd
{
	ll d,idz,idf;
	inline friend bool operator<(rg nd a,rg nd b) { return a.d<b.d; }
}t,d[maxn>>1];
bool sel[maxn],ans[maxn],pp;
int main()
{
	nn=n=read();m=read();for(rll i=1;i<=n;i++) a[i]=(node){read(),i};
	if(n&1) a[n].id=++n;sort(a+1,a+n+1);
	for(rll i=2;i<=n;i+=2) t=(nd){a[i].a-a[i-1].a,a[i].id,a[i-1].id},d[i>>1]=(t),sum+=t.d;
	if(sum&1||sum>=n) { Ch;return 0; } sum>>=1;
	sort(d+1,d+(n>>1)+1);
	for(rll i=n>>1;i;i--)
	{
		if(tot+d[i].d>sum) { id=i+1; break; } tot+=d[i].d;
	}
	sum-=tot;tot=0;
	for(rll i=1;i<=n>>1;i++)
	{
		if(tot+d[i].d>sum) { id2=i-1; break; } tot+=d[i].d;
	}
	for(rll i=1;i<=id2;i++) ans[d[i].idz]=1;
	for(rll i=id2+1;i<id;i++) ans[d[i].idf]=1;
	for(rll i=id;i<=n>>1;i++) ans[d[i].idz]=1;
	NP;for(rll i=1;i<=nn;i++) write(ans[i]?1:-1),put_;
	return 0;
}
  1. 爆搜,常数小的话可以卡过去
感谢kiritokazuto大佬的的代码
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
#define LL long long
#define Re register int
#define LD double
#define mes(x, y) memset(x, y, sizeof(x))
#define cpt(x, y) memcpy(x, y, sizeof(x))
#define fuc(x, y) inline x y
#define fr(x, y, z)for(Re x = y; x <= z; x ++)
#define fp(x, y, z)for(Re x = y; x >= z; x --)
#define delfr(x, y, z)for(Re x = y; x < z; x ++)
#define delfp(x, y, z)for(Re x = y; x > z; x --)
#define frein(x) freopen(#x ".in", "r", stdin)
#define freout(x) freopen(#x ".out", "w", stdout)
#define ki putchar('\n')
#define fk putchar(' ')
#define WMX aiaiaiai~~
#define pr(x, y) pair<x, y>
#define mk(x, y) make_pair(x, y)
#define pb(x) push_back(x)
#define re(x) return x
#define sec second
#define fst first

using namespace std;
namespace kiritokazuto{
    auto read = [](){
        LL x = 0;
        int f = 1;
        char c;
        while (!isdigit(c = getchar())){ if (c == '-')f = -1; }
        do{ x = (x << 1) + (x << 3) + (c ^ 48); } while (isdigit(c = getchar()));
        return x * f;
    };
    template  <typename T> fuc(void, write)(T x){
        if (x < 0)putchar('-'), x = -x;
        if (x > 9)write(x / 10); putchar(x % 10 | '0');
    }
}

using namespace kiritokazuto;


const int maxn = 1e6 + 10, maxm = 505;
int n, m;
struct Node {
    int id, val;
    friend bool operator < (Node A, Node B) {return A.val < B.val;}
}a[1000010];
bool is[1000010];
LL sum = 0;
fuc(bool, dfs)(int x){
    
    if (sum == 0) return 1;
    fp(i, x, 1) {
        if(a[i].val > sum)continue;
        sum -= a[i].val;
        
        if(dfs(i - 1)) {
            is[a[i].id] = 1;
            return 1;
        }
        sum += a[i].val;
    }
    return 0;
}
signed main(){
	n = read(), m = read();
    fr(i, 1, n) {
        a[i].val = read();
        a[i].id = i;
        sum += a[i].val;
    }
    sort(a + 1, a + n + 1);
    sum >>= 1;
    int fg = dfs(n);
        printf("NP-Hard solved\n");
        fr(i, 1, n) {
            if(is[i])printf("-1 ");
            else printf("1 ");
        }
	return 0;
}

D. 校门外歪脖树上的鸽子(512MB 2000ms)

把原二叉树进行树剖得到两条重链,开两棵线段树,一颗计算右链对左儿子的贡献,另一颗计算左链对右儿子的贡献.

选出节点 p 的过程只会影响到 [l,r] 区间,就相当于在线段树上进行区间加和. 查询也是同理,在线段树上进行区间查询.

懒的打了,代码就暂咕着吧.

posted @ 2022-09-25 17:30  1Liu  阅读(25)  评论(0编辑  收藏  举报