2021 ICPC上海站 D(思维),G(猜结论),I(01背包变形)

14 篇文章 0 订阅

题目
D
题意: 给定p/q,求是否存在两个正整数a、b,满足a/b+b/a=p/q.
思路: 假设x=a/b,然后发现就是个一元二次方程,然后如果Δ不是某个数的平方的话,是不符合题意的,因为x不能是无理数的。
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,k,T;
//set<ll> sa;
void solve()
{
	ll p;
	int q; scanf("%lld%d",&p,&q);
	ll t = 1ll*p*p-4ll*q*q;
//	ll tmp = sqrt(t);
	if(t>=0)
	{
		ll tmp = sqrt(t); 
		if(tmp*tmp!=t) {printf("0 0\n"); return ;}
		int x = p+tmp;
		int y = 2*q;
		printf("%d %d\n",x,y);
	}
	else printf("0 0\n");
}
int main(void)
{
	scanf("%d",&T);
	while(T--)
	solve();
	return 0;	
} 

G
题意: 给定一棵n个节点的树,保证n是奇数,然后我们需要把这n-1条边分成互不相交的n-1/2组,即每两条边在一个组里,然后要求是这两条边必须有一个公共顶点
思路: 猜结论。先随便找个点当根,dfs预处理每个节点的深度。之后统计答案。叶子不算,从叶子往上的一层开始。如果有偶数个儿子,自己分配,Cn2、Cn-22这样的,然后如果是奇数个儿子,留着一个儿子和该节点连向父节点的边配合,剩下的选俩、选俩直到都选完。
PS: 记得除(tot)!,tot是每个节点分了多少组,因为这样算有会有重复。没考虑到,输。
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
const int mod = 998244353;
int n,m,k,T;
vector<int> va[N],v[N];
int level[N];
int mx;
int d[N];
int f[N];
ll inv[N];
ll fac[N];
void dfs(int cur,int fa)
{
//	cout<<cur<<":"<<l<<"??\n";
	f[cur] = fa; level[cur] = level[fa] + 1;
	for(int i=0;i<va[cur].size();++i)
	{
		int j = va[cur][i];
		if(j==fa) continue; 
		dfs(j,cur);
	}
	v[level[cur]].push_back(cur);
	mx = max(mx,level[cur]);
}
ll qpow(ll a,int k)
{
	ll res = 1;
	while(k)
	{
		if(k&1) res = res * a % mod;
		k >>= 1;
		a = a * a % mod;
	}
	return res;
}
ll ni(int x)
{
	return qpow(x,mod-2);
}
void solve()
{
	ll ans = 1;
	scanf("%d",&n);
	for(int i=0;i<n-1;++i)
	{
		int x,y; scanf("%d%d",&x,&y);
		va[x].push_back(y),va[y].push_back(x);
		d[x]++,d[y]++;
	}
	int root = 1;
	dfs(root,0);
	for(int t=mx;t>=1;--t)
	{
		for(int i=0;i<v[t].size();++i)
		{
			int u = v[t][i];
			
			int tot = 0;
//			if(d[u]&1) tot = (d[u]-1)/2;
//			else tot = (d[u]-2)/2;
//			cout<<u<<":"<<d[u]<<" "<<tot<<":"<<inv[tot]<<"\n";
			d[u]--;
			while(d[u]>=2)
			{
				ans = ans * 1ll * d[u] % mod * (d[u]-1) % mod * ni(2) % mod;
				d[u] -= 2;
				tot ++ ;
			}
			ans = ans * inv[tot] % mod;
			if(d[u]==1) d[u]--,d[f[u]]--;
		}
	}
	ans %= mod; ans = (ans + mod) % mod;
	printf("%lld",ans);
}
int main(void)
{
	fac[0] = inv[0] = 1;
	for(int i=1;i<N;++i)
	{
		fac[i] = fac[i-1] * i % mod;
	}
	inv[N-1] = ni(fac[N-1]);
	for(int i=N-2;i>=1;--i)
	{
		inv[i] = inv[i+1] * (i+1) % mod;
	}
	solve();
	return 0;	
} 

I
题意: 给定n个物品,有体积和价值。可以选m个体积翻倍。任选一些物品分成两个不相交的集合,满足两个集合的体积和相等的前提下,使得价值和最大。
思路: 01背包变种。
f[i][j][k]: 前i个物品,有j个翻倍,然后集合1的体积和-集合2的体积和为k。
注意可能是负数,所以要整个偏移量。
状态转移的话,每个物品有选、集合1以及翻倍不翻倍、集合2以及翻倍不翻倍。
代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 102;
int n,m,k,T;
ll f[N][N][N*26*2];
int v[N];
int w[N];
int sum = 0;
void solve()
{
	ll ans = 0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d%d",&w[i],&v[i]),sum += v[i];
//	for(int i=1;i<=n;++i) scanf("%d",&v[i]),sum += v[i]; 
	sum *= 2;
	memset(f,-0x3f,sizeof(f));
	f[0][0][sum] = 0; 
	for(int i=1;i<=n;++i)
	{
		for(int j=0;j<=min(i,m);++j)
		{
			for(int k=-sum+sum;k<=sum+sum;++k)
			{
				f[i][j][k] = max(f[i][j][k],f[i-1][j][k]); //不选
				if(k-v[i]>=0&&k-v[i]<=2*sum) 
				f[i][j][k] = max(f[i][j][k],f[i-1][j][k-v[i]]+w[i]); //选左
				if(j>=1&&k-2*v[i]>=0&&k-2*v[i]<=2*sum) 
				f[i][j][k] = max(f[i][j][k],f[i-1][j-1][k-2*v[i]]+w[i]); //选左且翻倍 
				if(k+v[i]>=0&&k+v[i]<=2*sum)
				f[i][j][k] = max(f[i][j][k],f[i-1][j][k+v[i]]+w[i]); //选右 
				if(j>=1&&k+2*v[i]>=0&&k+2*v[i]<=2*sum) 
				f[i][j][k] = max(f[i][j][k],f[i-1][j-1][k+2*v[i]]+w[i]); //选右且翻倍 
			}
		}
	}
//	cout<<f[1][0][sum-1]<<"???\n";
	for(int j=0;j<=m;++j)
	{
//		cout<<j<<":"<<f[n][j][sum]<<"?\n";
		ans = max(ans,f[n][j][sum]);
	}
	printf("%lld",ans);
}
signed main(void)
{
	solve();
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值