暑假の刷题3

上一篇文章超字数了属于是;
所以这篇八千字发表了;

POJ2411

蒙德里安的梦想,状压DP经典题目了属于是;
很恶心这道傻逼题目;
状态设计:f[i][j]表示在i行状态是j的方案数 ;
状态J是什么样的呢,就是说J里面是一的位置就代表那个位置又一个横着的向下一行伸出去的块;
状态转移:
我们每次枚举到一行,就枚举一下他的前面一行;然后进行一个判断是吧;
判断的时候要满足这么几个条件:
1.J & K == 0;
2.其实可以预处理一下所有的状态,怎么个预处理法,就是每个状态来说进行逐位的判断,如果说有一位是0就直接启动计数器,如果说到出现1 的时候cnt是个奇数马上退出就行了;
最后的结果,因为最后一排肯定是不能伸出去的所以就是f[n][0];

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
#define int long long 
const int N = 15,	M = 1<<12;
int h,w;
int map[M];
int f[N][M];
void prework(int w){
	for(int i=0;i < 1<<w;i++) {
		
		int num_0 = 0;
		bool flag = true;
		for(int j=0;j<w;j++) {
			if(    !( 1&(i >> j) )  ) {
				num_0 ++ ;
//				printf("%d\n",j);
			}else {
				if(num_0 % 2) {
//					printf("final:%d\n",j);
					flag = false;	break;
				} else {
//					printf("reset:%d\n",j);
					num_0 = 0;
				}
			}
		}
		if(num_0 % 2) flag =false;
		if(flag) map[i] = true;
	}
}
signed main()
{
	while(1){
		scanf("%lld%lld",&h,&w);
		if(h==0 && w==0 ) return 0;
		
		memset(map,0,sizeof map);
		prework(w);
		memset(f,0,sizeof f);
		for(int i=0;i< 1<<w ;i++){
			if(map[i]) f[1][i] = true;
		}
		
		for(int l=2;l<=h;l++){
			for(int i=0;i < 1<<w ;i++){
				for(int j=0;j < 1<<w ;j++){
					if(i & j) continue;
					int k = i | j;
					if(map[k]) f[l][i] += f[l-1][j];
				}
			}
		}
		printf("%lld\n",f[h][0]);   
 	}
	return 0;
}

有些小细节要注意下,还有开longlong;

POJ2486

一看这个题目以为是二叉苹果树,翻译之后哦发现不是这样的,这题就是一个树上背包啊;初始化为0就行了,我们要设计两个数组,第一个是g[u][i];
表示节点u向下的在拥有i条边的时候的权值最大值;f[u][i]就表示挂在那个节点的权值最大值;当然我们这里要使用泛化物品的方法;
简而言之就是说,第二次递归上来的时候呢,对于一个节点f要重复更新好多次;总归就是这样代码里见;

以上因为题目看错了,全是错的,这告诉我们看题目很重要;

这题有点难度的点在于他是要求你要走k步,就是说有可能是往回走的那种;
以前好像碰到过的,好像挺难的,当时一点都没想出来;
重新想了下发现路径分为这三种情况;每种情况对于每一个顶点来说是要从那个顶点出发的;
值得注意的一点是,对于向下的情况来说,要找出最好的和次好的,因为对于向上的来说确实是应该这样啊;
还有一个结论,1如果遍历整颗子树,那么步数就是那颗子树的边的数量乘以二;数学归纳法容易证明;

在这里插入图片描述
又来否定我自己了;
以上做法全是错的,写的时候发现根本无从下手;其实向上的,那个状态是根本多余的完全不需要那个状态,我们求的是一条路,不是树的重心或者是中心;
我们只需要表示出这条路径上的最高点就可以了;
在这里插入图片描述
最大路径有可能出现这种情况,所以只需要两种状态,第一种是回头第二种是不回头,0回头1不回头表示,不回头的可以由不回头转变而来也可以由回头的转变而来,而回头的只能由回头的转变而来,因为你不知道不回头的话他一共要走多远的距离;
然后是一个状态转移的过程,就是做一次背包,怎么背包就看代码了;
现在想到其实上面那幅图还是不是完整,其实右边还能再加上一根不回头的线,所以还要多开一个状态2,表示仅仅最高点是u,然后就是既不从u出发又不从u结尾的状态;
这回没错了,正解也是这么想的,只不过遇到了一个问题,那就是题目又没看完整,有个note说从1出发,写完了才看到。。。

但是我这样写的话,那就是不管从哪个点出发都可以了,肯定是能求出最大的最大值的,从1出发只是一个可以顺带求出来的小分支而已;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 405;
int f[5][N][N];
int e[N],ne[N],w[N],h[N],idx;
int n,k;
void add(int a,int b){
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u,int fa){
	for(int i=0;i<=k;i++){
		f[0][u][i] = w[u];//i和1不要混了,前面de了十来分钟才de出来
		f[1][u][i] = w[u];
		f[2][u][i] = w[u];
	}//这样写保证不会让重复计算w,不要放在状态转移方程里;
	//能放在状态转移方程的情况是方程中可以转移过来的那项本来不应该包含w
	for(int i=h[u]; ~i ; i=ne[i]) {
		int j = e[i]; if(j == fa) continue;
		dfs(j,u);
		for(int a=k;a>=0;a--){
			for(int b=a;b>=0;b--){
//以下内容第一次其实不是这样写的;
//网上找了个AC码来对拍
//最开始我是a-b-2这样写的,这样子写显然就是填表法了,但是填表法不太行
//ba的大小不好控,有RE的风险,于是我们只能使用刷表法更加方便;
//于是写成是a+1 b+1的形式
				f[2][u][a+1] = max( f[2][u][a+1],	f[1][u][b] + f[1][j][a-b]);
				f[2][u][a+2] = max( max( f[2][u][a+2],f[2][u][b] + f[0][j][a-b]),f[1][u][b] + f[0][j][a-b] );
		
				f[1][u][a+1] = max(f[1][u][a+1], f[0][u][b] + f[1][j][a-b]);
				f[1][u][a+2] = max(f[1][u][a+2], f[1][u][b] + f[0][j][a-b]);
				

			    f[0][u][a+2] = max(f[0][u][a+2],f[0][u][b] + f[0][j][a-b]);
			}
		}
	}
}
int main()
{
	while( cin >> n >> k){
		idx = 0;
		memset(f,0,sizeof f);
		memset(h,-1,sizeof h);
		memset(w,0,sizeof w);
		
		for(int i=1;i<=n;i++){
			scanf("%d",&w[i]);
		}
		for(int i=2;i<=n;i++){
			int a,b;
			scanf("%d%d",&a,&b);
			add(a,b);	add(b,a);
		}
		int ass = -0x3f3f3f3f;
		dfs(1,-1);
		
		for(int i=0;i<=k;i++){
			ass = max(ass, max( f[1][1][i] ,f[0][1][i]));
		}
	    
	 //   printf("test1:%d    ",f[0][5][k]);
	    
		printf("%d\n",ass);
	}
	return 0;
}

这题很有代表意义;

POJ2057

题目看不懂,先去杀数据结构了;

POJ2777

不知道这题谁出的;
lr的顺序有可能是反的,样例每件题面没说;
我只能祝你:


#include<iostream>
#include<cstdio>
#include<bitset>
using namespace std;
#define int long long 
const int N = 100010;
struct node{
	int l,r;
	int add;
	int color;
}tr[N*4];
int n,m,t;
void pushup(int u){
	tr[u].color = tr[u<<1].color | tr[u<<1|1].color;
}void pushdown(int u){
	node &root = tr[u], &left = tr[u<<1], &right = tr[u<<1|1];
	if(root.add) {
		left.add = root.add, left.color = ( 1<<(root.add-1) );
		right.add = root.add,right.color =( 1<<(root.add-1) );
		root.add = 0;
	}	
}void build(int u,int l,int r){
	if(l==r)  tr[u].l = r, tr[u].r = r,tr[u].color = 1;
	else{
		tr[u].l = l , tr[u].r = r;
		int mid = l+r >> 1;
		build(u<<1,l,mid);	build(u<<1|1,mid+1,r);
		pushup(u);
	}	
}void modify(int u,int l,int r,int d){
	if(tr[u].l >= l&& tr[u].r <= r ){
		tr[u].color =  (1<<(d-1));
		tr[u].add = d;
	}else{
		pushdown(u);
		int mid = (tr[u].l + tr[u].r ) >> 1;
		if(l <= mid) modify(u<<1,l,r,d);
		if(r >  mid) modify(u<<1|1,l,r,d);
		pushup(u);
	}	
}int query(int u,int l,int r){
	if(tr[u].l >= l && tr[u].r <= r) return tr[u].color;
	pushdown(u);
	int tmp = 0,num = 0;
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid)  tmp = query(u<<1,l,r);
	if(r > mid) tmp |= query(u<<1|1,l,r);
	return tmp;
}		
signed main()
{
	scanf("%lld%lld%lld",&n,&t,&m);
	build(1,1,n);
	char op[2];
	int l,r;
	while(m--){
		scanf("%s%lld%lld",op,&l,&r);
		if(l >= r) swap(l,r);
		if(*op == 'C'){
			int change;
			scanf("%lld",&change);
			modify(1,l,r,change);
		}else{
			int tmp = query(1,l,r), num = 0;
			for(int i=0;i<=t;i++){
				if( 1& (tmp >> i))	num ++;
			}
			printf("%lld\n",num);
		}
		
	}
}
/*
100 10 5
C 2 12 2
C 11 24 3
Q 1 24
C 23 25 4
Q 1 25
*/

简单的,如果用bitset的话,还是10W的数据,那么大概能解决颜色到10000的题目,10000/32/400差不多刚好为1左右;

这题lr在搞反之后就0分了,这启示我们一定要交换,最好不管题目说没说都去换一下,不然正式考试出现这种问题就爆炸了;

POJ2886

乍一看这个题目哪里线段树了,区间都没有;
想了想才发现,如果说你把一个东西抽走之后,对于这个圈子里面的所有人,他左边的第A个人都会集体改变,还有就是人可能会很多,但是每个顺序出来的人的score都是一样的,所以我们只需要搞到因子最多的那个数就可以停止了;
想了挺久的,终于想出来了,其实不能硬想,去暴力模拟一下这么过程就能发现哪里需要优化,然后就能写了;
这题就是化环为链,然后每搞掉一个打上一个tag,然后下一个数,查一下他的路径上已经有几个tag了,然后把它的目标数加上或者减去这个tag就行了,可以用树状数组来写,小常数很轻便啊;

但是,这上面的都是我没有开始写的时候的想法,写了之后发现还是很有不同的,因为你无法直接判断你的旁边的第k个,所以你必须要二分出来,还有其实断环为链的想法其实是不对的,因为你无法保证那个k一定能在两个周期里面就能解决,所以正解应该是取下mod,写了好久的代码全报废了;
还有输名字的问题,直接来char数组储存就行了,然后输出也方便的不用string搞烦死;

然后就是那个样例一定要先去推一遍然后再去写代码,不然理解错了会很爽的,前面发现那个向左和向右其实是有歧义的,因为在圆里面向左和向右显然是有两种方式

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int mx = 5e5+5;
int anti_prim[] = {1,2,4,6,12,24,36,48,60,120,180,240,360,720,840,1260,1680,2520,5040,7560,10080,15120,20160,25200,27720,45360,50400,55440,83160,110880,166320,221760,277200,332640,498960,554400};
int num[] = {1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,60,64,72,80,84,90,96,100,108,120,128,144,160,168,180,192,200,216};
int n, k, c[mx];
struct node{
	char name[20];
	int value;
}stu[mx];
void add(int x,int val){
	for(;x<=n;x+=x&-x){
		c[x] += val;
	}
}
int ask(int x){
	int res = 0;
	for(;x;x -=x&-x) res += c[x];
	return res;
}
int find(int x){
	int l = 1,r = n;
	while(l < r) {
		int mid = (l+r) >> 1;
		if(ask(mid) >= x) r = mid;
		else l = mid + 1;
	}
	return l;
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%s %d",stu[i].name,stu[i].value);
		add(i,1);
	}
	int pos = lower_bound(anti_prim,anti_prim+36,n) - anti_prim;
	if(anti_prim[pos] > n ) pos -- ;
	int now = k,next = k,remaining = n - 1, cnt = 1, ass;
	add(k,-1);
	while(true){
		if(cnt == anti_prim[pos]){
			ass = now; break;
		}
		int m = stu[now].value;
		if(m > 0) next = (next - 1+ m) %remaining;
		else next = ((next+m)%remaining+remaining) % remaining;
		if(next == 0) next = remaining;
		remaining --; cnt ++;
		now = find(next);
		add(now,-1);
	}
	printf("%s %d\n",stu[ass].name,num[pos]);
	return 0;
}

POJ2352

刚看的时候没什么思路,没思路其实可以打暴力来做的,因为数据范围刚好超过1W一点,运气好就过了呢,而且这个暴力常数似乎不是很大;
然后想到二维前缀和,想多了;
现在是平衡树做法,二维偏序,以X为第一关键字,Y为第二关键字来排序,答案一定在那个数的位置的前面那段,这时候找一下那一段Y比他小的就行了,平衡树线段树树状数组都很好解决;
不过我还是有点疑惑,为什么树状数组能解决的问题要用平衡树解决;

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
int n;
struct star{
	int x,y;
}a[20000];
int tr[80000];
int level[80000];
bool cmp(star A,star B) {
	if(A.x == B.x) return A.y < B.y;
	return A.x < B.x;
}
void add(int pos,int x){
	for(int i=pos;i<40000;i += i&-i )	tr[i] += x;
}
int ask(int pos){
	int res = 0;
	for(int i=pos;i;i -= i&-i) res += tr[i];
	return res;
}
signed main()
{
	while(cin >> n){
		
		memset(a,0,sizeof a);
		memset(tr,0,sizeof tr);
		memset(level,0,sizeof level);
		
		for(int i=1;i<=n;i++)	scanf("%lld%lld",&a[i].x,&a[i].y);
		sort(a+1,a+1+n,cmp);
		for(int i=1;i<=n;i++){
//			printf("x:%d    y:%d\n",a[i].x,a[i].y);
			level [ask(a[i].y+1)] ++ ;
			add(a[i].y+1,1);
//			printf("val:%d\n",ask(a[i].y) - 1);	
		}
		for(int i=0;i<n;i++){
			printf("%lld\n",level[i]);
		}
	
	}
	return 0;
}

其实又一个很坑的点就是,0&0会死的,Z*/0也会死的,所以那个要加一,上面对应的树状数组的范围不是n,是32000!!!!!!!这里非常容易搞混!!!!!!!!
最后给出题人献上诚挚的问候:

POJ3321

做法一树链剖分,每个点的信息移到链接他和她父亲的那条边上;
每次输入到根节点的路径然后直接暴力;
做法二数组加上链表,对于每个点,额外储存一下他的父节点,然后每次修改沿着这条链一直上去到根节点为止,两个复杂度是NlogN,稳过了属于是;
UPDATE 1 HOUR LATER 我是小丑,来根链就是必死无疑;
想了好久没想出logn的做法,上网找了下做法是dfs序,想的时候想到这题很有可能跟dfs序是有关的,但是接下来再怎么做就想不到了;
代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N =100010, M = N*2;
int e[M],tr[M],ne[M],w[M],h[M],start[N],end[N];
int n,idx,m,time;
void add(int a,int b){
	e[idx] = b,ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u,int father){
	start[u] = ++time;
	w[u] = 1;
	for(int i=h[u];~i;i=ne[i]){
		int j = e[i];
		if(j == father) continue;
		dfs(j,u);
	}
	end[u] = time;
}
int ask(int x){
	int res = 0;
	for(int i=x;i;i -= i&-i ){
		res += tr[i];
	}
	return 	res;
}
void modify(int pos,int val){
	for(int i=pos; i<=n; i += i&-i ) tr[i] += val;
}
int main()
{
	memset(h,-1,sizeof h);
	scanf("%d",&n);
	int x,y;
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);	add(y,x);
	}
	dfs(1,-1);
	for(int i=1;i<=n;i++) modify(i,1);
	scanf("%d",&m);
	char op[2];int u;
	while(m--){
		scanf("%s%d",op,&u);
		if(*op == 'Q'){
			printf("%d\n",ask(end[u]) - ask(start[u] - 1));
		}else{
			if(w[u]){
				w[u] = 0;
				modify(start[u],-1);
			}else{
				w[u] = 1;
				modify(start[u],1);
			}
		}
	}
	return 0;
}

其实就是要树状数组灵活转换一下,果然我思维狭隘,想不到这种东西;

POJ2482

放在平衡树里面,告诉我是扫描线。。。。。标签打错是真的马死。
扫描线做不太来,先放一会再回来做。
UPDATE:这题和扫描线都放在另一篇文章里了;

POJ1195

Y总说的好,提高组数据结构的题都没什么脑子,乱做就行了,这题直接二维树状数组,蚌阜住了;

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int S = 1030;
int op,s,x,y,a;
int l,b,r,t;
int tr[S][S];
void add(int line ,int pos,int x){
	for(; pos <= s ;pos += pos& -pos){
		tr[line][pos] += x;
	}
}
int ask(int line,int pos){
	int res = 0;
	for(;pos;pos -= pos&-pos){
		res += tr[line][pos];
	}	return res;
}
int main()
{
	scanf("%d%d",&op,&s);
	while(1){
		scanf("%d",&op);
		if(op == 3) return 0;
		else if(op == 1){
			scanf("%d%d%d",&x,&y,&a);
			
			x++,y++;
			
			add(x,y,a);
		}else if(op == 2){
			
			scanf("%d%d%d%d",&l,&b,&r,&t);
			
			l++,b++,r++,t++;
			
			int res = 0;
			for(int i=l;i<=r;i++){
				res += ask(i,t) - ask(i,b-1);
			}
			printf("%d\n",res);
		}
	}
	return 0;
} 
/*
0 4
1 1 2 3
2 0 0 2 2 
1 1 1 2
1 1 2 -1
2 1 1 2 3 
3
*/

吗的卡常奇迹了这是;

POJ3264

ST婊裸题;就是注意细节,log里面最好先转换成double,不然像POJ这样的破地方就会CE;

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 200010, M = 18;
int n,q;
int w[N];
int f[N][M],g[N][M];	
void prework(){
	for(int j=0;j<M;j++){
		for(int i=1; i+(1<<j)-1<=n;i++){
			if(!j)f[i][j] = w[i];
			else f[i][j] = max(f[i][j-1],f[i+(1<<j-1)][j-1]);
//			printf("i:%d   j:%d   val:%d\n",i,j,f[i][j]);
		}
	}
	for(int j=0;j<M;j++){
		for(int i=1; i+(1<<j)-1<=n;i++){
			if(!j)g[i][j] = w[i];
			else g[i][j] = min(g[i][j-1],g[i+(1<<j-1)][j-1]);
		}
	}
}
int query(int l,int r){
	int len = r - l + 1;
	int k = (double)log((double)len) / (double)log((double)2);
	return max(f[l][k],f[r-(1<<k) + 1][k]) - min(g[l][k],g[r-(1<<k) + 1][k]);
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&w[i]);
	}
	prework();	
	int a,b;
	while(q--){
		scanf("%d%d",&a,&b);
		if(a>b) swap(a,b);
		printf("%d\n",query(a,b));
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值