暑假の刷题2

POJ1942

这题还挺有意思哈,开始就直接想DP,一看内存30MB??????
过于狗屎了属于是,于是直接组合数,因为你走到一个点,所需要的走的横向的步数和纵向的步数是确定下来的,所以说可以直接整活了属于是 ,循环一下求阶乘,乘乘除除出答案;

POJ292

这个题目选的同余系是惩罚封闭的,诶,舒服,筛一遍质数,然后分别枚举两个质数给那些semi质数打上一个tag然后就完事了;

POJ1845

挺好一道题,只不过做的时候脑子抽了,公式忘了,自己退了遍有个地方还推错了就尼玛离谱是吧;
其实就是套一下公式解一下等比数列的乘积但是这题由于非常操蛋所以我们必须要使用快速幂和龟速乘,但是求等比数列的时候我们有两种方法第一种是错位相减法第二种则是递归分治法,两种都挺不戳的;
附一下代码:


在这里插入图片描述

#include<iostream>
#include<cstdio>
#define int long long 
using namespace std;
const int mod = 9901,N = 50000005;
int vis[50003],prime[50003],tot = 0;
int A,B,ass,a,res;
void init(){
	for(int i =2;i<=50000;i++){
		if(!vis[i]) prime[ ++ tot] = i;
		for(int j = 1;j<= tot && i*prime[j] <= 50000 ; j++){
			vis[i*prime[j]] = 1;
			if( i % prime[j] == 0) break;
		}
	}
}
int mul(int a,int x,int p){
	int ass = 0;
	for(;x;x >>= 1){
		if(x&1) ass = (ass+a) % p;
		a = (a + a) % p;
	}
	return ass;
}
int power(int a,int b,int p){
	int ass= 1;
	for(;b;b >>= 1 ){
		if(b&1) ass = mul(ass,a,p) % p;
		a = mul(a,a,p) % p;
	}
	return ass;
}
signed main()
{
	init();
	scanf("%lld%lld",&A,&B);
	res = 1 ; a = A;
	for(int i = 1;prime[i] * prime[i] <= a;i++){
		if(a % prime[i] == 0){
			int k = 0;
			while(A%prime[i] == 0){
				k ++; A /= prime[i];
			}
			int p = mod * (prime[i] - 1);
			res = res * (power(prime[i], k*B+1 ,p) - 1) / 
			(prime[i] - 1);
			res %= mod ;
		}
	}
	if( A > 1){
		int p = mod *(A-1);
		res = res * (power(A,B+1,p) - 1) / (A - 1);
		res %= mod;
	}
	printf("%lld\n",res);
	return 0;
}

分治法求等比数列之和是吧;拿捏拿捏;

int sum(int p,int q,int mod){
	if(q == 0) return 1;
	if (q&1) {
		return sum(p,q/2,mod) * (1 + power(p,q/2+1,mod)) % mod;
	} else {
		return ((1 + pwer(p,q/2,mod)) * sum(p,q/2-1,mod)
		+ power(p,q,mod)) % mod;
	}
}

POJ2115

那啥扩欧的好题,我又脑子出问题了,本来可以列个方程扩欧解决,然后我想到了再自己去意淫一种奇怪的方法?????
不过也不算亏,想这个方法的过程还是有点要思维的,也总结出来几个结论;
我想的方法是针对于这类问题,K不一定是2^K,行吧;

1.如果C是K的因子,直接根据两个数的差来判断吧;
2.如果不是那么直接把C和K来进行GCD然后剩下两个互质的是吧,再次循环结果得到A就需要C / GCD(C,K) 次,然后在那个K的系里面呢,δ*GCD(C,K) +AmodGCD 都可以得到啊,δ为任意整数,如果B不在这个集合里面直接过就行了,如果B在里面的话,Bmod一下C 再去除以 GCD 就是答案了;

希望我这是最后一次用到这个傻逼结论了;

update 5min after 第二点只对了一半,判断有没有解的情况,后面算答案的那个方法错的,只能暴力了;

正解是构造一个二元一次方程直接解就行了,构造方法还是挺多的;
只介绍一种;

A+nC-B=m2^k
nC-m2^k=B-A
然后扩展gcd

附上扩欧一个;

void exgcd(int a,int b,int &x,int &y){
	if(b == 0){
		x = 1, y = 0, return a;	
	}
	int d = exgcd(b,a%b,x,y);
	int t = x, x = y, t - a/b*y;
	return d;
}

这就是徐倞舟!!!

POJ3273

二分,干就完了,没别的;
不知道这题放出来意义何在。。。

POJ3258

这题跟上一题有点相似但是又不是完全一样好吧,二分一下就行了;
但是我之前想的是二分一下,然后遍历间隔长度,也就是遍历线段,然后不满足条件的线段去和它的两边比较短的那条合并一下,对的,但是很烦;
最好的方法是枚举石头所在的端点,我就想我怎么这么脑残这种显而易见的东西都没有想到;

POJ1905

实数域上的二分,乍一看式子列出来之后感觉好寄吧奇怪啊,试着去找了下不用这么解方程的方法好像找不出来,那大概就只能实数二分解方程了;
实数域上的二分还是用for比较好因为while会玄学问题很多;

int l=1e-10,r=1e10;
for(int i=1;i<=100;i++){
    int mid=(l+r)/2;
    if( check() ) l=mid;
    else r=mid;
}

POJ3122

二分,算能搞几块,大于等于F就行了;

POJ1191

仅此纪念我第一道自己做出来的紫色DP,其实洛谷上面紫色和蓝色差不多的,就是多了一个自己推公式的环节,看到那个公式的,应该都会下意识拆出来吧。。。

DP的状态设计讲究一个阶段性和最优子结构性质,阶段性很明显的体现在割到第几刀的这么一个过程上,最开始我想的是已经构想出来几刀切了的情况,然后呢在可以割的区域里面遍历再割,但是这么一想,哦不,这不最优子结构,这很不DP,于是想到我对于一块矩形来说,我之前怎么割是不知道的,那么我就可以枚举上一步过来是怎么割的,显然这非常DP,而且一定能找到最优解,虽然我现在写的时候才想到证明,刚才做的时候没去想了,刚才做的时候就是,想到这么DP的方法,肯定是对的,打了下真的是对的;

最后一点,这题很卡精度,最后算的时候要尽量的化简公式以此避免小数乘来乘去,而且int类型作为除数的时候一定要化成是double不然直接上天;
拿捏拿捏;
送上亲手代码;

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int f[10][10][10][10][20];
int sum[20][20],tot[20][20];
int a[20][20],b[20][20];
int get (int x1, int y1, int x2, int y2) {
	int res = tot[x2][y2] - tot[x1-1][y2] 
	- tot[x2][y1-1] + tot[x1-1][y1-1];
	return res * res;
}
int n;
int main()
{
	scanf("%d",&n);
	
	n--;
	
	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++){
			scanf("%d",&a[i][j]);
			b[i][j] = a[i][j] * a[i][j];
			sum[i][j] = sum[i-1][j] + sum[i][j-1]
			 - sum[i-1][j-1] + b[i][j];
			tot[i][j] = tot[i-1][j] + tot[i][j-1] 
			- tot[i-1][j-1] + a[i][j];
//			printf("i:%d    j:%d   val:%d\n",i,j,sum[i][j]);
		}
	}
	
//	printf("%d\n",a[8][8]);
	memset(f,0x3f,sizeof f);
	f[1][1][8][8][0] = 0;
	for(int s=1;s<=n;s++){
		for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++){
			for(int x=i;x<=8;x++){
			for(int y=j;y<=8;y++){
				for(int k=1;k<=8;k++){
					if(k<i){
						f[i][j][x][y][s] = min(f[i][j][x][y][s], 
							f[k][j][x][y][s-1] + get(k,j,i-1,y));
					} else if(k>x) {
						f[i][j][x][y][s] = min(f[i][j][x][y][s],
							f[i][j][k][y][s-1] + get(x+1,j,k,y));
					}
				}
				for(int k=1;k<=8;k++){
					if(k<j){
						f[i][j][x][y][s] = min(f[i][j][x][y][s],
							f[i][k][x][y][s-1] + get(i,k,x,j-1));
					} else if(k>y){
						f[i][j][x][y][s] = min(f[i][j][x][y][s],
							f[i][j][x][k][s-1] + get(i,y+1,x,k) ); 
					}
				}
				
			}
			}
		}
		}
	}
	int ass = 0x3f3f3f3f;
	for(int i = 1;i<=8;i++){
	for(int j = 1;j<=8;j++){
		for(int x=i;x<=8;x++){
		for(int y=j;y<=8;y++){
			ass = min(ass,f[i][j][x][y][n]+get(i,j,x,y));
		}
		}
	}	
	}
//	printf("ass:%d\n",ass);
	double ave_x = (double)tot[8][8] / (double)(n+1);
//	printf("ave:%.3lf\n",ave_x);
    double tmp = (double)ass / (double)(n+1);
	double final_ass = tmp - ave_x*ave_x;
//	printf("final:%.3f\n",final_ass);
	printf("%.3lf\n",sqrt(final_ass));
	return 0;
}

POJ3280

做之前先立个flag,上次遇到的回文串那题啊,方法非常诡异,但是这题肯定是常规方法做就好了;
果然是的。。。
看了hint之后,我觉得这题会不会常规方法过不去啊,然后就过了。。。
过程想一下,意识流的证明还是能给出来的;

POJ2948

这个题目一看感觉好熟悉,然后发现就是当年洛谷上那道题意不清的狗逼题,谁转载的时候图没画,吗的根本不是人能看懂的;
可以仔细想下这个题目,如果说不准转弯,那么从最角落上落下去的矿肯定得是一条直线,所以那里肯定有条路直通两边,那么这个代价显而易见,然后剩下部分DP求解;

POJ1054

史上最玄学的一道题目,吗的,打了DP的tag正解是个暴力,我都不想说啥了,题目这么长他妈的还全寄吧都是英文实在是令人难以接受,想了两个多小时没想出来DP怎么搞,搜正解的时候才搜到暴力的做法是吧;
复制来的;暴力也是需要剪枝的;剪枝都在注释里了;

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define Max(a,b)((a)>(b)?(a):(b))
using namespace std;
const int maxn = 5000 + 10;
bool f[maxn][maxn];
int n, r, c, cx, cy;
struct node
{
    int x, y;
}p[maxn];

bool cmp(node a, node b)
{
    if(a.y == b.y)
    return a.x < b.x;
    else
    return a.y < b.y;
}
bool check(int x, int y)
{
    if(x>=1&&x<=r && y>=1&&y<=c)
    return true;
    return false;
}
int cal(int px, int py)
{
    int sum = 1;
    while(1)
    {
        if(!check(px+cx, py+cy))
        break;
        if(f[px+cx][py+cy])
        {
            sum++;
            px += cx; py += cy;
        }
        else
            return 0;
    }
    return sum;
}
int main()
{
    int i, j, ans;
    while(~scanf("%d%d", &r, &c))
    {
        memset(f, 0, sizeof(f));
        scanf("%d", &n);
        for(i = 0; i < n; i++)
            {
                scanf("%d%d", &p[i].x, &p[i].y);
                f[p[i].x][p[i].y] = true;
            }
        sort(p, p+n, cmp);

        ans = 2;
        for(i = 0; i < n; i++)
        for(j = i+1; j < n; j++)
        {
           cx = p[j].x - p[i].x; cy = p[j].y - p[i].y;
           if(check(p[i].x-cx, p[i].y-cy)) //判断是不是从稻田之外跳过来的
           continue;
           if(p[i].y+ans*cy>c) //因为y是递增的,
          //如果最大的ans 在稻田之外,后面也都大于
           break;
           if(!check(p[i].x+ans*cx, p[i].y+ans*cy))
           continue;  //这个不要写成break,
         //  因为x不是递增的,有可能前一个出界,但是后一个不出界。
           ans = Max(ans, cal(p[i].x, p[i].y));
        }
        if(ans < 3)
        printf("0\n");
        else
        printf("%d\n", ans);
    }
    return 0;
}

POJ2029

不知道这题怎么进题单的,傻逼题目。。。。。。
在这里插入图片描述

POJ3254

经典玉米田状压DP是吧,状压DP的主体部分大概是分这么几个步骤的,首先是枚举行,然后再是枚举行内状态,再枚举相关行的状态,然后判断相关状态对不对,然后再加上相关状态是吧,那个状态设计都是差不多一样的,区别是不大的;
但是又一个值得注意的点在于,枚举一行的状态的时候,因为有一些本来就是不行的,所以我们可以先预处理出来哪些状态是可以的装进一个vector里面就可以节省很多了;
判断的时候使用指数型枚举,然后用位运算来判断;

需要判断的地方有
1.玉米首先是不能重叠的;就是将两个状态 & 一下之后不能出现 1;
2.不能有相邻的玉米,这就说明是左移之后 & 一下 或者 右移之后 & 一下不能出现1,不能出现1 就是结果必须为0;
3.不能和场景有重合,就是说和场景的bit串与一下之后不能出现1,也就是必须为0;
状态设计就是f[I][J],J是压缩之后的状态,最后结果就是
f[N+1][0];
行吧;

POJ1925

留个坑,到时候再补,至今还未解决的一道玄学题目,为什么循环换个位置就会出错呢???实在是让人难以理解;

但是正解确实比我那个好,正解可以看出来比较明显的阶段性是吧,不至于像我那个一样那么傻逼;
其实可以发现这个题目跟01背包其实有那么一点相似,区别在于这题使用的是刷表法而不是填表法,01背包我们很容易就能写出来,显然是把物品作为阶段进行外层循环的,而这题里面就是将建筑作为阶段进行外层循环,比较符合背包的那个思维。
但是有一点就在于,其实01背包反着写也是能过的,为什么这题反着写就不行呢;
留到开学再填这个坑了;
为了满足我一万字的虚荣心附上一个代码;

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N = 5010, M = 2000010, INF = 0x3f3f3f3f;
int f[M],x[N],y[N],k,n;
signed main()
{
	scanf("%lld",&k);
	while(k--){
		scanf("%lld",&n);
		for(int i=1;i<=n;i++)	scanf("%lld%lld",&x[i],&y[i]);
		memset(f,0x3f,sizeof f);
		int ass = INF ;
		f[x[1]] = 0;
		for(int i=2;i<=n;i++){
			int s1 = (y[i] - y[1]) * (y[i] - y[1]);
			for(int j = x[i];;j++){
				int s2 = (j-x[i])*(j-x[i]);
				if (s2+s1 > y[i]*y[i]  )	break;
				if(2*x[i] - j >= x[1] ) 
					f[j] = min(f[2*x[i] - j]+1, f[j]);
				else break;
				if( j>= x[n]) ass = min (ass, f[j]);
			}
		}
		if(ass == INF)	printf("-1\n");
		else printf("%lld\n",ass);
	}
	return 0;
}

POJ3034

这个题目有点阴间好像啊,感觉没怎么描述清楚,后来看了下discuss才读懂,不巧的是discuss里面刚好有人提醒这个题目哪里有坑,所以失去了一次调到崩溃的机会,大概的步骤是这样子的;
首先根据d预处理出来能到的点的dx 和dy,然后求出每个dx和dy的gcd,然后每次转移的时候就是gcd加去加去就行了,加到dx和dy就直接停止然后这个就是这个路径上能锤到的地鼠;
这题可以用填表法也可以用刷表法,刷表法就是这么考虑你一个锤子能攻击到哪些范围,填表法就是你考虑一下你会可以从哪些地方过来攻击;
这题很幸运的一个点就在于,给你的攻击范围是一个圆形,那么被攻击到的范围其实也是一个圆形;有个规律,

攻击范围和受击范围是成中心对称的一个图形;

想到这个点回去想了下填表法和刷表法,其实就是一个中心对称的关系,所以说又想到了01背包,所以说01背包一共是有4种写法的,在随便想了下别的DP比如说上面那个玉米地,就可以用刷表法来做;是吧,很辩证,很马克思;

discuss里面有人提醒的坑的地方就在于说,坐标可以移动到负数的地方,想想还是可以想象出来一种情况符合这条的;
那就先写一下这题的代码吧;

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define debug puts("FUCKYOURASS")
const int N = 80,T = 20;
const int D = 20;
int n,d,m,cnt;
int a[T][N][N],dx[N*2],dy[N*2];
int f[T][N][N];
int gcd(int a,int b) {
	return b==0?a:gcd(b,a%b);
}
void prework (int d) {
	for(int i=-5;i<=5;i++){
		for(int j=-5;j<=5;j++){
			if(i*i + j*j <= d*d){
				dx[++cnt] = i;
				dy[cnt] = j;
			}
		}
	}
}
int main()
{
	while(1){
		memset(a,0,sizeof a);
		memset(f,0,sizeof f);
		memset(dx,0,sizeof dx);
		memset(dy,0,sizeof dy);
		cnt = 0;
		scanf("%d%d%d",&n,&d,&m);
		prework(d);
		if(n == 0 && d == 0 && m == 0) return 0;
		int x=0,y=0,t=0,max_t=0;
		for(int i=1;i<=m;i++){
			scanf("%d%d%d",&x,&y,&t);
			a[t][x+D][y+D] = 1;
			max_t = max(t,max_t);
		}
		int ass = -0x3f3f3f3f;
		for(int h=1;h<=max_t;h++){
			for(int i=-d-d;i<=n+d+d;i++){
				for(int j=-d-d;j<=n+d+d;j++){
					for(int k=1;k<=cnt;k++){
						int num = 0;
						int GCD =  gcd(dx[k],dy[k]) ;
						if(GCD<0) GCD = -GCD;
						if(GCD == 0){
							f[h][i+D][j+D] = 
							max(f[h-1][i+D][j+D] +
							 int ( a[h][i+D][j+D] == 1),f[h][i+D][j+D]);						
							continue;
						}
						int d_x = dx[k]/GCD,
							d_y = dy[k]/GCD;
						int now_x = i, now_y = j;
						while(now_x!=i+dx[k] || now_y!=j+dy[k]){
							if(a[h][now_x+D][now_y+D] == 1) {
								num ++;
							}
							now_x +=d_x,	now_y +=d_y;
																																			 
						}
						if(a[h][now_x+D][now_y+D] == 1)  num ++;
						f[h][i+dx[k]+D][j+dy[k]+D] = 
						max ( f[h][i+dx[k]+D][j+dy[k]+D],
						f[h-1][i+D][j+D] + num);
						ass = max(ass,f[h][i+dx[k]+D][j+dy[k]+D]);
					}
				}
			}
		}
		printf("%d\n",ass);
	}
	return 0;
}

这题的代码写了好久,写代码半个小时,debug大概五六个小时了属于是,我的马力是真的太烂了属于是,真的写的就是一个 bug;
现在列出我这题D出了哪些bug:
1.变量打错了,h打成了t;
2.数组开的不对,因为同一个位置不同时间可能会有很多只地鼠;
3.GCD乱改改错了;
4.GCD应该要绝对值没有搞上绝对值;
5.补上去的状态转移没有写完整;
6.预处理的时候,两个++cnt;
7.到达点的那个地鼠没有去考虑;
我们发现以上几个内容,基本上都是可以在写代码之前就可以考虑到的,除了有一些是写代码的细节,这告诉我们要先多想在写就会好很多;

POJ1185

炮兵阵地,经典老题了属于是啊,判断是和上面改一下,只不过在预处理合法状态的时候左移一位要改成是左移两位,右移一位也要改成右移两位;
然后这个不是求方案数了是吧,应该去求max,那么我们对于每个合法状态还要求出1的个数是吧;

状态设计就是f[I][J][K]i是行数,j是最外层状态,k是次外层状态,是吧;

做法就是先预处理,外层循环行数,内层循环第一层是我们要的那层,第二层第三层分别是次外层和次次外层,然后判断的时候比较狗啊条件挺多的:
1.最外层次外层,次次外层 & 上地图均为0;
2.最外层 & 上 次外层和次次外层的时候均要为 0 ;
3.行内见上;
然后填表;

最后的结果就是f[N+2][0][0];

还有一点,M的范围比较小,所以M拿来当行,这就是将增加量添加到系数和指数的区别了;

位运算这个东西比较好啊,补充几个位运算常用公式别人那里复制过来的;

1、获得第i位的数字:(a>>i)&1 或者 a&(1<<i)
很好理解,我们知道可以用&1来提取最后一位的数,那么我们现在要提取第i位数,就直接把第i位数变成最后一位即可(直接右移)。或者,我们可以直接&上1左移i位,也能达到我们的目的。
2、设置第i位为1:a=a|(1<<i)
我们知道强制赋值用|运算,所以就直接强制|上第i位即可。
3、设置第i位为0:a=a&(~(1<<i))
这里比较难以理解。其实很简单,我们知道非~运算是按位取反,(1<<i)非一下就变成了第i为是0,其它全是1的二进制串。这样再一与原数进行&运算,原数的第i位无论是什么都会变成0,而其他位不会改变(实在不明白的可以用纸笔进行推演)。
4、把第i位取反:a=a^(1<<i)
1左移i位之后再进行异或,我们就会发现,如果原数第i位是0,一异或就变成1,否则变成0。
5、取出一个数的最后一个1:a&(-a)
学过树状数组的同学会发现,这就是树状数组的lowbit。事实上,这和树状数组的原理是一样的。我想,不需要我多解释。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N = 110,M = 320;
int n,m,f[N][M][M];
int num[M],g[N];
char tmp;
vector<int> v;
void pre_work() {
	for(int i = 0; i < 1<<m;i ++ ){
		if( i & (i<<1) || i & (i<<2) || i & (i>>1) 
		|| i & (i>>2)) continue;
		int k = 0 ;
		for(int j=0;j<m;j++){
			if (1 & (i >> j) )	k ++ ;
		}
		num[i] = k;
		v.push_back(i);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++){
			cin>>tmp;
			if(tmp == 'H')	g[i] = g[i]|(1<<j);
			else g[i] = g[i]&(~(1<<j));
		}
	}
	
	pre_work();							
	for(int i=0;i<v.size();i++){
		if(v[i] & g[1] ) continue;
		for(int j=0;j<v.size();j++){
			if(v[j] & g[2] )continue;
			if(v[i] & v[j] )continue;
			f[2][ j ][ i ] = num[v[i]] + num[v[j]];  

			
		}
	}
	
	for(int h=3;h<=n+2;h++){
		for(int i=0;i<v.size();i++){
			if(v[i] & g[h] ) continue;
			for(int j=0;j<v.size();j++){
				if(v[j] & g[h-1] ) continue;
				for(int k=0;k<v.size();k++){
					if(v[k] & g[h-2] ) continue;
					if((v[i] & v[j]) || (v[j] & v[k]) 
					|| (v[i] & v[k])) continue;
					
					f[h][i][j] = max( f[h-1][j][k] + num[v[i]], 
					f[h][i][j] );
				}
			}
		}
	}
	printf("%d\n",f[n+2][0][0]);
	return 0;
}

要注意一个点,空间要拿捏拿捏,像前面那样我如果将状态作为状态表示里面的一个维度的话显然会超空间,数组开到1W了都,但是用vector里面的下标作为一个维度的话我们会发现这时候情况会好得多哈,有效状态此时只有60个,显然很稳了属于是;

POJ1947

用了高超的骗分技巧,只要输出一个2就能骗到40分;
01234加在一起能骗到94分,估计还有一个是构造数据,是一幅菊花图,割一条就少掉一个node的那种类型;
想一想感觉其实还是能写出来的,就是一个树上背包,只要枚举顶点做DP就好了,不需要什么优化,因为数据范围很小很小;
还是很简单的想一想就出来了,刚开始的时候想不出来,就先把代码敲起来了,然后再仔细想了想就出来了;

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 400;
int f[N][N],size[N];
int e[N],ne[N],h[N],deg[N];
int n,p,idx;
void add(int a,int b){
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs1(int u,int fa){
	size[u] = 1;
	for(int i=h[u];~i;i=ne[i]) {
		int j = e[i];	if (j == fa) continue;
		dfs1(j,u);
		size[u] += size[j];
	}
}
void dfs2(int u,int fa){
	f[u][size[u]] = 1;
	f[u][1] = deg[u];
//	printf("f[2][1]:%d\n",f[u][1]);
	for(int i=h[u];~i;i=ne[i]) {
		int j = e[i]; 	if (j == fa) continue;
		dfs2(j,u);
		for(int a = size[u]; a >= 1; a -- ){
			for(int b = 1;b<=a;b ++ ){
//				if(u==2 && a==2 && b == 1){
//				f[u][a],f[j][b],f[u][a-b],f[j][b]+f[u][a-b]-2);
//				}
				f[u][a] = min(f[u][a], f[j][b] + f[u][a-b] - 2);
//				if(u==2 && a==2 && b==1) printf("final:%d\n",f[u][a]);
			}
		}
	}
}
int main()
{
	memset(h,-1,sizeof h);
	memset(f,0x3f,sizeof f);
	scanf("%d%d",&n,&p);
	for(int i=1;i<n;i++){
		int a,b;	scanf("%d%d",&a,&b);
		add(a,b);	add(b,a);
		deg[a] ++ ,deg[b] ++ ;
	}
	dfs1(1,-1);
	dfs2(1,-1);
	int ass = 0x3f3f3f3f;
	
	for(int i=1;i<=n;i++){
		ass = min(ass,f[i][p]);
	}
	printf("%d\n",ass);
	return 0;
}

POJ3140

卧槽这个真的是狗屎题目一道,看的时候看M的范围,我就想这个东西跟树形DP是不是已经没有半毛钱关系了,还好后来看了下discuss,发现M只有N-1,于是就想到可以直接树形DP了,就是删边,使得两边点权最接近是吧;
这就一个贼简单甚至没什么意义的题目了;
做完发现我叼你妈的,我就是一个小丑,调了好久才调出来,值得一提的错误就是我ass初始化为1<<50是吧,然后发现居然挂了;
原来是要 1LL << 50才行;

刚好1W2;

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值