[绍兴集训2019]test2-15 (noip模拟题/动态规划专场)

文章目录

  • 吐槽
  • 题解
    • T1 分裂
    • T2 异或计数
    • T3 brainfuck

吐槽

一场简单的dp专场noip模拟题,可能是想给我这样的辣鸡选手一点自信,可是我没有领情。
正常选手得分:100+100+100
我的期望得分:100+100+30
我的实际得分:0+50+0
今年上半年我将继续扮演爆零士的角色,t1,t3双爆0。
t1对拍的时候输出了整个数组忘了注释掉,全场唯一一个t1爆零。
t2按n预处理2的幂,n=20的点幂不够挂了。没有前缀和优化后面T了。
t3打了暴力然后暴力写挂。

然后成功让大家看清了我弱智选手的本质。

题解

T1 分裂

发现价值和只跟分裂结果有关而和过程无关,最终价值和就是最后分裂出的所有小块两两相乘求和。
贪心可知分得尽量平均价值最大。
我是暴力dp求最大的分法, f [ i ] [ j ] f[i][j] f[i][j]表示i分成j份的最大价值,转移可用斜率优化,复杂度O(n^2).

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=1007;
typedef long long LL;
typedef double db;
using namespace std;
int S,M,s[N][N],que[N],ql,qr;
LL f[N],g[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
LL getx(int i) { return -i; }
LL gety(int i) { return f[i]-i*i; }
LL cross(LL ax,LL ay,LL bx,LL by) { return ax*by-ay*bx; }
LL get(int i,int j) { return f[i]+i*(j-i); }

int main() {
    freopen("split.in","r",stdin);
    freopen("split.out","w",stdout);
  	read(S); read(M);
  	For(i,1,S-1) {
  		ql=1; qr=0;
		For(x,1,S) {
			while(ql<qr&&get(que[ql],x)<=get(que[ql+1],x)) ql++;
			g[x]=get(que[ql],x);
			while(ql<qr&&cross(getx(x)-getx(que[qr-1]),gety(x)-gety(que[qr-1]),getx(que[qr])-getx(que[qr-1]),gety(que[qr])-gety(que[qr-1]))>=0) qr--;
			que[++qr]=x;
		}
		For(x,1,S) f[x]=g[x];
		//For(x,1,S) printf("%d ",f[x]);
		if(f[S]>=M) {
			printf("%d\n",i);
			Formylove;
		}
	}
	puts("-1");
	Formylove;
}

T2 异或计数

b i < = a i b_i<=a_i bi<=ai, b b b就是 a a a本身或者 a a a的某个最高位上的1变为0后,之后的位随意取。
设第 i i i个数最高的由1变为0的位为 f [ i ] f[i] f[i],那么 i i i之后的数小于 f [ i ] f[i] f[i]的位无论是多少, b i b_i bi都有办法把他们变成正确的样子,且存在唯一方案。我们维护当前所有数中最大的一个 f f f,加入一个新数 i i i,若 f i f_i fi小于当前 f f f,不做任何更新且 b i b_i bi 2 f [ i ] 2^{f[i]} 2f[i]种选择方式。
f i f_i fi大于 f f f,由之前的数来保证比 f f f小的位合法,当前 i i i数的 f f f之前的位就可以随便选, 2 f 2^f 2f种方案,当前数来保证 f f f~ f i + 1 f_i+1 fi+1合法,唯一方案,更新现在的 f f f
最终最大的一个 f f f没有数来保证它合法,所以最大的 f f f一定有偶数个,我们还要记录当前 f f f的奇偶性。
用dp来做这样的维护, f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑到第 i i i个数,当前最大的 f f f j j j且奇偶性为 k k k的方案数。
然后如果你睿智地没有前缀和来保证更新复杂度,就会T掉后三个点。
如果你更加睿智地预处理 2 2 2的幂时处理到 n n n,就会WA掉n<=20的点。

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=1e5+7,p=1000000009;
typedef long long LL;
typedef double db;
using namespace std;
int n;
LL f[N][32][2],a[N],pr[N],sum[N];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

LL mo(LL x) { return x<0?x+p:(x>=p?x-p:x); }

int main() {
    freopen("xor.in","r",stdin);
    freopen("xor.out","w",stdout);
    read(n);
    For(i,1,n) read(a[i]);
    pr[0]=1;
    For(i,1,30) pr[i]=pr[i-1]*2%p;
	For(j,0,30) if(a[1]&(1LL<<j)) f[1][j][1]=1;
	For(i,2,n) {
		For(j,0,30) if(a[i]&(1LL<<j)) f[i][j][1]=1;
		sum[0]=(a[i]&1)?1:0;
		For(k,1,30) {
			if(a[i]&(1LL<<k)) sum[k]=mo(sum[k-1]+pr[k]);
			else sum[k]=sum[k-1];
		}
		For(j,0,30) {
			f[i][j][0]=mo(f[i][j][0]+f[i-1][j][0]*sum[j-1]%p);
			f[i][j][1]=mo(f[i][j][1]+f[i-1][j][1]*sum[j-1]%p);
		}
		LL tpsum=0;
		For(k,0,30) {
			if(a[i]&(1LL<<k)) {
				f[i][k][0]=mo(f[i][k][0]+f[i-1][k][1]*pr[k]%p);
				f[i][k][1]=mo(f[i][k][1]+f[i-1][k][0]*pr[k]%p);
				f[i][k][1]=mo(f[i][k][1]+tpsum);
			}
			tpsum=mo(tpsum+mo(f[i-1][k][0]+f[i-1][k][1])*pr[k]%p);
		}
		For(k,0,30) For(j,0,1)
			f[i][k][j]=mo(f[i][k][j]+f[i-1][k][j]);
	}
	LL ans=0;
	For(i,0,30) ans=mo(ans+f[n][i][0]);
	ans=mo(ans+1);
	printf("%lld\n",ans);
	Formylove;
}

T3 brainfuck

其实这题比T2简单,但是一开始T1想得不太顺利加不是很会写斜率优化,前两题耗费太多时间导致T3没什么时间了,也是写了两个dp看见又是dp头都大了打个暴力就走了,暴力还打挂了。。

1。如果括号里面有括号,要么(a)这个括号一开始就是直接从左端跳到右端不会进去,要么(b)外括号最多执行一次不会循环执行(考虑里面最后一个括号执行完出来一定为0,那么这个外括号执行到最后的结果无论几次循环都不改变)。
2。也就有循环执行的括号内部一定无镶嵌,全是±。

根据1我们可以把所有括号看成无镶嵌的形式,a可以忽略括号内的内容看成单括号(内部只需满足括号匹配,预处理出长度为1~n的满足括号匹配的序列。或者是2无镶嵌的全±,仍然预处理。),b可以忽略外括号看成顺序执行里面的语句(那么b中的外括号只要匹配就好了)。
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示长度为 i i i的序列,b中未匹配的括号数为 j j j,当前 x x x的值为 k k k的方案数。

//Achen
#include
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=107;
typedef long long LL;
typedef double db;
using namespace std;
int n,p;
LL f[N][N][N*2],g[N],gg[N][N],h[N][N*2];

template<typename T> void read(T &x) {
	char ch=getchar(); T f=1; x=0;
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') f=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

LL mo(LL x) { return x<0?x+p:(x>=p?x-p:x); }
LL F(int x) { return x+100; }
void pls(LL &x,LL y) { x=mo(x+y); }

int main() {
    freopen("brainfuck.in","r",stdin);
    freopen("brainfuck.out","w",stdout);
    read(n); read(p);
    
    gg[0][0]=1;
    For(i,0,n-1) For(j,0,i) if(gg[i][j]) {
    	pls(gg[i+1][j],gg[i][j]*2%p);
    	pls(gg[i+1][j+1],gg[i][j]);
    	if(j) pls(gg[i+1][j-1],gg[i][j]);	
    }
    g[1]=0;
    For(i,2,n) g[i]=gg[i-2][0];
    
    h[0][F(0)]=1;
    For(i,0,n-1) For(j,-i,i) if(h[i][F(j)]) {
    	pls(h[i+1][F(j+1)],h[i][F(j)]);
    	pls(h[i+1][F(j-1)],h[i][F(j)]);
    }
    
    f[0][0][F(0)]=1;
    For(i,0,n-1) For(j,0,i) For(x,-i,i) if(f[i][j][F(x)]) {
    	LL tp=f[i][j][F(x)];
    	pls(f[i+1][j][F(x+1)],tp);
    	pls(f[i+1][j][F(x-1)],tp);
    	if(x==0) {
    		if(j) pls(f[i+1][j-1][F(x)],tp);
    		For(k,2,n-i) pls(f[i+k][j][F(x)],tp*g[k]%p);
    	}
    	else {
    		pls(f[i+1][j+1][F(x)],tp);
    		For(y,-i,i) if(abs(y)!=0&&abs(x)%abs(y)==0&&abs(x)!=abs(y)&&x*y<0) {
    			For(k,3,n-i) 
    				pls(f[i+k][j][F(0)],tp*h[k-2][F(y)]%p);		
    		}
    	}
    }
    LL ans=0;
    For(i,-n,n) ans=mo(ans+f[n][0][F(i)]);
    printf("%lld\n",ans);
    Formylove;
}

你可能感兴趣的:(绍兴集训,动态规划)