buctoj周赛14

4516 Problem A 家庭作业

思路

设置一个无法完成的时间期限 t i m e time time(截止时间在此之前的作业都无法完成,即 1 − t i m e 1-time 1time都已被使用),设置 v i s vis vis数组,标记第 i i i天是否已使用,将作业按照学分从大到小排列,按顺序遍历,若 t > t i m e t>time t>time,对于每项作业从 t t t到1枚举天数,若能找到未被标记的一天,标记它并将该作业学分加入 s u m sum sum,若不能( 1 − t 1-t 1t都已被使用),更新 t i m e = t time=t time=t,最后输出 s u m sum sum.

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct test{
	int t,s;
}a[1000005];
bool vis[700005];
bool cmp(test a,test b)
{
	 return a.s>b.s ;	
}
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;++i)
	{
		cin>>a[i].t>>a[i].s;
	}
	sort(a,a+n,cmp);int ti=0,sum=0;
	for(int i=0;i<n;++i)
	{
		if(a[i].t<=ti) continue;
		int flag=0;
		for(int j=a[i].t;j>=1;--j)
		{
			if(vis[j]==false)
			{
				vis[j]=true;
				sum+=a[i].s;
				flag=1;break;
			}
		}
		if(flag==0) ti=a[i].t;
	}
	cout<<sum;
 } 

另一个思路:用并查集查找
将每一天的 f f f指向自己(未被使用),若已被使用则将 f f f指向前一个未被使用的日子。同样将作业按照学分从大到小排列,按顺序遍历,从 t t tk开始找父亲,若找到 x = f [ x ] x=f[x] x=f[x],更新 f [ x ] f[x] f[x],标记一下返回,若 x < = 0 x<=0 x<=0,无标记返回。最后输出 s u m sum sum.

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct test{
	int t,s;
}a[1000005];
int f[700005],flag;
bool cmp(test a,test b)
{
	 return a.s>b.s ;	
}
int find(int x)
{
	if(x<=0) return x;
	else if(x==f[x])
	{
		f[x]=x-1;
		flag=1;
		return f[x];
	}else 
	return f[x]=find(f[x]);
}
int main()
{
	for(int i=0;i<=7000005;++i)
	f[i]=i;
	int n,sum=0;cin>>n;
	for(int i=0;i<n;++i)
	cin>>a[i].t>>a[i].s; 
	sort(a,a+n,cmp);
	for(int i=0;i<n;++i)
	{
		flag=0;
		f[a[i].t]=find(a[i].t);
		if(flag) sum+=a[i].s;
	}
	cout<<sum;
} 

4518 Problem B 糖果传递

思路

输入数据同时对小朋友的糖果数量求和,可以计算出每个小朋友最后得到 a v e ave ave
假设第 i i i个的小朋友有 a i ai ai颗糖果,第 i i i个小朋友给了第 i − 1 i-1 i1个小朋友 x i xi xi颗糖果,如果 x i < 0 xi<0 xi<0,说明第 i − 1 i-1 i1个小朋友给了第 i i i个小朋友 x i xi xi颗糖果, x 1 x1 x1表示第一个小朋友给第 n n n个小朋友的糖果数量。 所以最后的答案就是 r e s = ∣ x 1 ∣ + ∣ x 2 ∣ + ∣ x 3 ∣ + C + ∣ x n ∣ res=|x1| + |x2| + |x3| + C+ |xn| res=x1+x2+x3+C+xn
对于第1个小朋友,他给了第 n n n个小朋友 x 1 x1 x1颗糖果,还剩 a 1 − x 1 a1-x1 a1x1颗糖果;第2个小朋友又给了他 x 2 x2 x2颗糖果,所以最后第1个小朋友还剩 a 1 − x 1 + x 2 a1-x1+x2 a1x1+x2颗糖果。
所以得 a 1 − x 1 + x 2 = a v e a1-x1+x2=ave a1x1+x2=ave
同理,可得 n n n个方程,变形得
第1个小朋友: x 2 = a v e − a 1 + x 1 = x 1 − c 1 x2=ave-a1+x1 = x1-c1 x2=avea1+x1=x1c1(假设 c 1 = a 1 − a v e c1=a1-ave c1=a1ave,下面类似)
第2个小朋友: x 3 = a v e − a 2 + x 2 = 2 a v e − a 1 − a 2 + x 1 = x 1 − c 2 x3=ave-a2+x2=2ave-a1-a2+x1=x1-c2 x3=avea2+x2=2avea1a2+x1=x1c2
第3个小朋友: x 4 = a v e − a 3 + x 3 = 3 a v e − a 1 − a 2 − a 3 + x 1 = x 1 − c 3 x4=ave-a3+x3=3ave-a1-a2-a3+x1=x1-c3 x4=avea3+x3=3avea1a2a3+x1=x1c3
……
第n个小朋友, a n − x n + x 1 = a v e an-xn+x1=ave anxn+x1=ave
若让 x i xi xi的绝对值之和尽量小,即 ∣ x 1 ∣ + ∣ x 1 − x 1 ∣ + ∣ x 1 − c 2 ∣ + … … + ∣ x 1 − c n − 1 ∣ |x1| + |x1-x1| + |x1-c2| + ……+ |x1-cn-1| x1+x1x1+x1c2++x1cn1要尽量小。注意到 ∣ x 1 − c i ∣ |x1-ci| x1ci的几何意义是数轴上的点 x 1 x1 x1 c i ci ci的距离,即给定数轴上的n个点,找出一个到他们的距离之和尽量小的点,而这个点就是这些数中的中位数。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[1000005],c[1000005]; 
int main()
{
	ll n;scanf("%lld",&n);
	ll ave=0,res=0;
	for(int i=1;i<=n;++i)
	{
	scanf("%lld",&a[i]);ave+=a[i];
	}ave=ave/n;
	for(int i=1;i<n;++i)
	{
		c[i]=c[i-1]+a[i]-ave;
	}
	sort(c,c+n);
	ll  mid;
	if(n%2)
	mid=c[n/2];
	else mid=(c[(n-1)/2]+c[(n+1)/2])/2;
	for(int i=0;i<n;++i)
	res+=abs(c[i]-mid);
	printf("%lld",res);
 } 

4696 Problem C 骑士

连接每个骑士与其厌恶的骑士,得到一个图。对于图中的每个联通块,设其节点数为 k k k,则它的边数一定 ≤ k ≤k k(可能会有重边,所以边数可能不到 k k k),这就意味着每个联通块不是一棵树就是一棵树上任意连一条边。
对于每个联通块:
①如果它是一棵树,考虑树形 d p dp dp.
d p [ i ] [ j ] dp[i][j] dp[i][j]表示以 i i i为根的子树中, i i i计不计入答案(计入⇒ j = 1 j=1 j=1,不计入⇒ j = 0 j=0 j=0)的最大战斗力,转移方程为
d p [ i ] [ 0 ] dp[i][0] dp[i][0] = = = ∑ j ∈ s o n i m a x \sum_{j∈soni} max jsonimax{ d p [ j ] [ 0 ] , d p [ j ] [ 1 ] dp[j][0],dp[j][1] dp[j][0],dp[j][1]}
d p [ i ] [ 1 ] dp[i][1] dp[i][1] = = = f i g h t i + fighti+ fighti+ ∑ j ∈ s o n i \sum_{j∈soni} jsoni d p [ j ] [ 0 ] dp[j][0] dp[j][0]
答案为
m a x d p [ r o o t ] [ 0 ] , d p [ r o o t ] [ 1 ] max{dp[root][0],dp[root][1]} maxdp[root][0],dp[root][1]
②如果它是一棵树加一条边,我们可以先找到联通块上的唯一的环,任意删去其中的一条边 u , v u,v u,v,就得到了一棵树,鉴于 u , v u,v u,v间本应有边,所以我们考虑不取 u u u或不取 v v v,最后取 m a x max max就能知道联通块的答案,即分别以 u , v u,v u,v为根做一遍 d p dp dp,最后最大战斗力为 m a x max max{ d p [ u ] [ 0 ] , d p [ v ] [ 0 ] dp[u][0],dp[v][0] dp[u][0],dp[v][0]}.
最终答案即为每个联通块的答案之和。
PS:注意输入时去掉重边.
参考博客,详见【BZOJ1040】【ZJOI2008】骑士 题解

学习链接

树形动态规划题集
基环树DP

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<map>
using namespace std;
vector<int>G[1000010];
int f[1000010];
bool vis[1000010];
int hte[1000010];
long long dp[1000010][2];
int n;
int u,v;
int dep[1000010];
long long ans;
void dfs(int x,int p)
{
    vis[x]=true;
    for(int i=0;i<G[x].size();i++)
    {
        int y=G[x][i];
        if(y==p)continue;
        if(vis[y])
        {
            if(dep[y]<dep[x])u=x,v=y;
            continue;
        }
        dep[y]=dep[x]+1;
        dfs(y,x);
    }
}
void solve(int x,int p)
{
    dp[x][1]=f[x];
    dp[x][0]=0;
    for(int i=0;i<G[x].size();i++)
    {
        int y=G[x][i];
        if((x==u && y==v) || (x==v && y==u) || y==p)continue;
        solve(y,x);
        dp[x][0]+=max(dp[y][0],dp[y][1]);
        dp[x][1]+=dp[y][0];
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d%d",f+i,hte+i);
        if(hte[hte[i]]==i)continue;
        G[hte[i]].push_back(i);
        G[i].push_back(hte[i]);
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i])continue;
        u=v=0;
        dfs(i,0);
        long long res;
        if(!u && !v)
        {
            solve(i,0);
            res=max(dp[i][0],dp[i][1]); 
        }
        else
        {
            solve(u,0);
            res=dp[u][0];
            solve(v,0);
            res=max(res,dp[v][0]);
        }
        ans+=res;
    }
    printf("%lld",ans);
    return 0;
}

4697 Problem D 牧场的安排

思路

简单状压 d p dp dp,二进制表示每块地的状态。
d p [ i ] [ j ] dp[i][j] dp[i][j]表示第 i i i j j j状态时前i行的方案数, d p [ 0 ] [ 0 ] = 1 dp[0][0] = 1 dp[0][0]=1作为边界(什么格子都不种的方案)。状态转移:如果本行的 j j j状态和上一行的 k k k状态不冲突,那么本行 j j j状态的方案数就加上上一行 k k k状态的方案数,即 k k k状态可以到 j j j状态。
d p [ i ] [ j ] + = d p [ i − 1 ] [ k ] dp[i][j]+=dp[i-1][k] dp[i][j]+=dp[i1][k]
因为两块相邻的地不能种草,对数据进行预处理。当 i i i表示每行地的状态时,(( i i i<<1)& i ) = = 0 i)==0 i)==0表示没有两块地相邻土地状态。用 v e c t o r vector vector数组保存所有可能的合法状态。当 i i i表示这一行的状态, j j j表示上一行的状态时, i i i& j = = 0 j==0 j==0就表示这两行没有地相邻。
i i i表示这一行的状态, j j j表示这一行是否可以种草的状态时, i i i& j = = i j==i j==i就说明i状态是可行的,假如 i i i& j ! = i j!=i j!=i,那么就存在 i i i状态的某一列是1,但是这一块地本身是不能种植的。用数组s来保存这一行是否可以种草的状态。
最后统计 d p [ n ] [ 所 有 状 态 ] dp[n][所有状态] dp[n][]的总方案数。

学习链接

状态压缩dp(状压dp)
状态压缩动态规划 状压DP

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,dp[13][401],a[13][13],s[13];
const int mod=1e8;
vector<int>v;
int main()
{
	int n,m;
	scanf("%d%d",&m,&n);
	for(int i=0;i<(1<<n);++i)
	if(((i<<1)&i)==0)
	v.push_back(i);
	for(int i=1;i<=m;++i)
	for(int j=0;j<n;++j)
	scanf("%d",&a[i][j]);
	for(int i=1;i<=m;++i)
	for(int j=0;j<n;++j)
	s[i]=(s[i]<<1)+a[i][j];
	dp[0][0]=1;
	for(int i=1;i<=m;++i)
	{
		for(int j=0;j<v.size();++j)
		{
			if((v[j]&s[i])==v[j])
			{
				for(int k=0;k<v.size();++k)
				{
					if((v[k]&v[j])==0)
					{
						dp[i][j]+=dp[i-1][k];
						dp[i][j]%=mod;
					}
				}
			}
		}
	}ll res=0;
	for(int i=0;i<v.size();++i)
	res=(res+dp[m][i])%mod;
	printf("%lld",res);
} 

4924 Problem E 宝藏

思路

选择合适的方案,满足已经属于同一个联通块中的两点间不会有直接相连的第二条边,同时给定两点间连边的代价,为 L L L* K K K,找到一个连边的顺序,最终使得所有点联通同时总代价最小( L ∗ K L*K LK为起点到当前点的所经过的节点数乘当前边长度)
对于每个点选或不选的状态,用状压解决。
f [ s ] f[s] f[s]表示状态 s s s下,使得这些选择的点在同一个联通块中的最小代价,则转移方程:(从 i i i点转移到 j j j点)
f f f[1<<( j − 1 j-1 j1)| s s s]= f [ s ] + d i s [ i ] ∗ g [ i ] [ j ] f[s]+dis[i]*g[i][j] f[s]+dis[i]g[i][j]
其中 g [ i ] [ j ] g[i][j] g[i][j]表示 i , j i,j i,j两点间道路的距离, d i s [ i ] dis[i] dis[i]表示距离 i i i根节点间的节点数.
搜索求出最优解,以每个点为根都进行一次 d f s dfs dfs+状压 d p dp dp,枚举每一个点是否被选。
参考博客,详见:[NOIP2017]宝藏-动态规划,状压dp,搜索,二进制用法模板,dfs

4767 Problem F 取石子游戏 1

思路

巴什博奕(Bash Game):
n < = k n<=k n<=k,必然先手胜,若 n = k + 1 n=k+1 n=k+1,必然后手胜,对 n n n,必有 n = ( k + 1 ) ∗ r + s n=(k+1)*r+s n=(k+1)r+s,所以只要先手取走 s s s个,将 r ∗ ( k + 1 ) r*(k+1) r(k+1)局面留给后手,则先手可以胜,若 s = 0 s=0 s=0,先手面对 r ∗ ( k + 1 ) r*(k+1) r(k+1)局面,则后手胜。
n n n%( m + 1 ) = = 0 m+1)==0 m+1)==0. 后手胜,反之,先手胜。

学习链接

博弈论之取石子游戏的学习

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
	int n,k;cin>>n>>k;
	if(n%(k+1)) cout<<'1';
	else cout<<'2';
} 

4771 Problem G 巧克力棒

思路:

n i m nim nim游戏
n i m nim nim游戏一个很神奇的结论:对于一个局面,当且仅当 a [ 1 ] x o r a [ 2 ] x o r . . . x o r a [ n ] = 0 a[1] xor a[2] xor ...xor a[n]=0 a[1]xora[2]xor...xora[n]=0时,该局面为P局面,即必败局面。
取巧克力棒:
我们取出来最长异或子序列,留给对手的是个必败局面or 他只能再拿巧克力棒,再拿巧克力棒的话局面就变成必胜了,你只需要再把局面变成 a [ 1 ] x o r a [ 2 ] x o r . . . x o r a [ n ] = 0 a[1] xor a[2] xor ...xor a[n]=0 a[1]xora[2]xor...xora[n]=0.

代码

#include<bits/stdc++.h>
using namespace std;
int a[2002],panduan=0,n;
int dfs(int deep,int chang,int juge)
{
	if(deep==n+1)
	{
		if(chang>0&&!juge)
		panduan=1;
		return 0;
	}
	dfs(deep+1,chang,juge);
	dfs(deep+1,chang+1,juge^a[deep]);
}
int main()
{
int b,c,d,e,f,g,h,i,j,k,l,m;
for(i=1;i<=10;i++)
{cin>>n;panduan=0;
memset(a,0,sizeof(a));
for(b=1;b<=n;b++)
cin>>a[b];
dfs(1,0,0);
if(panduan)cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
return 0;
}

学习链接

博弈论(一):Nim游戏
博弈论基础之sg函数与nim

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值