思维风暴篇

题目链接:https://cn.vjudge.net/contest/298926

A - Minimum Array

CodeForces - 1157E
题意: 两个长度为n的数组,数组a位置不变,改变数组b的位置,构造数组c ci=(ai+bi)%n,使得数组c字典序最小。
题解: 使得数组c的字典序最小,即每次bi使得ai+bi大于n并且最接近n,因此用multiset维护数组b,lower_bound(n-a[i])即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=200010;

int n;
int a[maxn],b[maxn],c[maxn];
int num[maxn];

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
    multiset<int> s;
    for (int i = 0; i <n; ++i) {
        scanf("%d", &b[i]);
        s.insert(b[i]);
    }
    for (int i = 0; i <n; ++i) {
        int temp = n - a[i];
        multiset<int>::iterator pos = s.lower_bound(temp);
        if (pos == s.end()) {
            c[i] = (a[i] + *s.begin());
            s.erase(s.begin());
        } else {
            c[i] = (a[i] + *pos) % n;
            s.erase(pos);
        }
    }
    /*
    for(int i=0;i<n;i++)
        printf("%d\n",num[i]);
    /**/
    for(int i=0;i<n;i++)
        printf("%d ",c[i]);
	return 0;
}

B - Serval and Rooted Tree

CodeForces - 1153D

题解转载自:https://blog.csdn.net/Link_Ray/article/details/89332969
题意
给出一颗树,有k个叶子结点,每个叶子结点的权值为1~k中的一个,除叶子结点外,每个结点都有min或者max操作,表示取其儿子的最小值或最大值,求根结点最大值是多少。
题解
dp[i]: 表示以i为根的这个子树中叶子结点里第dp[i]大的值。
对于max和min操作贪心的选取。
max: dp[u] = min{dp[v]}
min: dp[u] += dp[v]
要使dp尽量小

如果是max操作,有一个儿子为第2大,一个儿子为第3大,这两个儿子都是相对于他们自己的子树而言的,当他们合并的时候,那么相当于叶子结点中,我们对5个数作比较,而传上来的只有左边第2大,右边第3大,那么就取左边第2大。即5个数中第2大的。
如果是min操作,同理,这时候也是对5个数作比较,这显然取最后的数,即第5大的。

总结:这种1~k的问题一般都转化为第几大来解决。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
const int maxn=300010;
#define inf 0x3f3f3f3f

int a[maxn];
vector<int> ve[maxn];
int dp[maxn];
int cnt,n;
void dfs(int u)
{
	if(ve[u].size()==0){
		dp[u]=1;
		cnt++;
		return;
	}
	dp[u]=0;
	if(a[u]) dp[u]=inf;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		dfs(v);
		if(a[u])
			dp[u]=min(dp[u],dp[v]);
		else
			dp[u]+=dp[v];
	}
}
int main()
{
	while(~scanf("%d",&n)){
		cnt=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			ve[i].clear();
		}
		int u;
		for(int i=2;i<=n;i++){
			scanf("%d",&u);
			ve[u].push_back(i);
		}
		dfs(1);
		printf("%d\n",cnt-dp[1]+1);
	}
}

C - Problem for Nazar

CodeForces - 1151C

参考题解:https://www.cnblogs.com/zaq19970105/p/10750208.html#undefined

题目大意:
  有一个只存奇数的集合A = {1, 3, 5……2n - 1,……},和只存偶数的集合B = {2, 4, 6……2n,……},现在要生成一个序列,这个序列分成i次生成:第1次从A中取1个放入序列,第2次从B中取2个放入序列,第3次从A中取4个放入序列,……,第2n - 1次从A中取22n-2个放入序列,第2n次从B中取22n-1个放入序列……。取得顺序是从小到大取的。题目给定一个区间,就序列在该区间所有数的累加和。
分析:
  只要实现一个求区间[1, i]的函数getSum(i),那么任意区间内的和都可以算出,比如区间[l, r]的和为getSum® - getSum(l - 1)。
  设odd_len为区间[1, i]上奇数的个数,even_len为区间[1, i]上偶数的个数。
  只要求出了odd_len和even_len就可以用直接用等差数列求和了。
下面给出序列1~40的数:
在这里插入图片描述
假设x = 37(100101),x落在偶数部分,even_len = 2 + 8 + 6 = 10 + 1000 + 101 + 1 = (11111 & 01010) + (11111 & x) + 1,odd_len = 1 + 4 + 16 = 11111 & 10101
假设x = 25(11001),x落在奇数部分,odd_len = 1 + 4 + 10 = 1 + 100 + 1001 + 1 = (1111 & 0101) + (1111 & x) + 1,even_len = 2 + 8 = 1111 & 1010
规律还是比较好找的,直接文字叙述太麻烦,直接给例子比较容易理解。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
const int maxn=300010;
#define inf 0x3f3f3f3f
const ll mod=1e9+7;
const ll evenBits=0xaaaaaaaaaaaaaaaa;
const ll oddBits=0x5555555555555555;

ll getBits(ll x)
{
	ll res=1;
	while(x>>=1){
		res++;
		//x>>=1;
	}
	return res;
}
ll quickpower(ll x,ll y)
{
	ll res=1;
	while(y){
		if(y&1)
			res=res*x%mod;
		y>>=1;
		x=x*x%mod;
	}
	return res;
}
const ll ONE=1;
ll getsum(ll x)
{
	if(x==0) return 0;
	ll len=getBits(x);
	ll even_len,odd_len,ret=0;
	if(len%2==0){
		even_len=(((1LL<<(len-1LL))-1LL)&x)+(((1LL<<(len-1LL))-1LL)&evenBits)+1;
		odd_len=x-even_len;
	}else{
		odd_len=(((1LL<<(len-1LL))-1LL)&x)+(((1LL<<(len-1LL))-1LL)&oddBits)+1;
		even_len=x-odd_len;
	}

	odd_len %= mod;
	even_len %= mod;
	
    ll inv2=quickpower(2LL,mod-2LL);
    //等差数列求和 
    ll an=(2LL+(even_len-1)*2LL%mod)%mod;
    ret=(ret+((2LL+an)*even_len%mod*inv2%mod))%mod;
    an=(1LL+(odd_len-1)*2LL%mod)%mod;
    ret=(ret+((1LL+an)*odd_len%mod*inv2%mod))%mod;
    return ret % mod;
}
int main()
{
	ll l,r;
	while(~scanf("%lld%lld",&l,&r)){
		ll res=getsum(r)-getsum(l-1);
		res=(res+mod)%mod;
		printf("%lld\n",res);
	}
}

F - The Beatles

CodeForces - 1142A
题意:给一个有n*k个点的环,环上从1开始,每k个点有一个餐厅,你每次单向移动L个点,现在不知道你的初始位置和L的值,但是知道你初始位置离最近的餐厅的距离a和你第一次移动之后离最近的餐厅的距离b。你移动若干次后能回到起点,求这个值的最小值和最大值。
(1)L=mk+b-a
(2)L=mk-b-a
( n ∗ k ) (n*k) (nk)=L(%L)那么移动次数= l c m ( n ∗ k , l ) / l = n ∗ k / g c d ( n k , l ) , n &lt; = 1 e 5 lcm(n*k,l)/l=n*k/gcd(nk,l),n&lt;=1e5 lcm(nk,l)/l=nk/gcd(nk,l)n<=1e5,暴力枚举即可

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long
const int maxn=100010;
#define inf 0x3f3f3f3f

int n,k;


ll gcd(ll a,ll b)
{
	while(b){
		ll tmp=a%b;
		a=b;
		b=tmp;
	}
	return a;
}
int main()
{	
	while(~scanf("%d%d",&n,&k)){
		ll a,b;
		scanf("%I64d%I64d",&a,&b); 
		ll y=1LL*n*k;
		ll mn=10000000000,mx=1;
		for(ll i=1;i<=n;i++){
			ll tmp1=y/gcd(y,i*k-a-b);
			ll tmp2=y/gcd(y,i*k+b-a);
			while(tmp1<0) tmp1+=k;
			while(tmp2<0) tmp2+=k;
			mn=min(mn,tmp1);
			mn=min(mn,tmp2);
			mx=max(mx,tmp1);
			mx=max(mx,tmp2);
		}
		printf("%I64d %I64d\n",mn,mx);
	}
	return 0;
}

G - Same Sum Blocks (Easy)

CodeForces - 1141F1
给定一个序列,求最大不相交的、相同区间和的区间数
题解:把每个区间的值都储存起来,把有相同区间和的区间放一起,贪心去掉有重叠的区间,取最大值即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=55;

int n,sum[maxn];
int a[maxn*maxn];
struct node{
	int l,r;
}d;
bool cmp(const node& a,const node& b)
{
	if(a.r!=b.r) return a.r<b.r;
	return a.l>b.l;
}
vector<node> tmp,ans,b[maxn*maxn];
int main()
{
	while(~scanf("%d",&n)){
		sum[0]=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&sum[i]);
			sum[i]+=sum[i-1];
		}
		int tot=0;
		for(int i=1;i<=n;i++){
			for(int j=i;j<=n;j++){
				a[tot++]=sum[j]-sum[i-1];
			}
		}
		sort(a,a+tot);
		tot=unique(a,a+tot)-a;
		for(int i=0;i<tot;i++)
			b[i].clear();
		for(int i=1;i<=n;i++){
			for(int j=i;j<=n;j++){
				int k=lower_bound(a,a+tot,sum[j]-sum[i-1])-a;
				//if(k>=tot) continue;
				d.l=i;d.r=j;
				b[k].push_back(d);
			}
		}
		ans.clear();
		for(int i=0;i<tot;i++){
			sort(b[i].begin(),b[i].end(),cmp);//
			int pre=0;
			tmp.clear();
			for(int j=0;j<b[i].size();j++){
				if(pre<b[i][j].l){
					tmp.push_back(b[i][j]);
					pre=b[i][j].r;
				}
			}
			if(tmp.size()>ans.size())
				ans=tmp;
		}
		printf("%d\n",ans.size());
		for(int i=0;i<ans.size();i++)
			printf("%d %d\n",ans[i].l,ans[i].r);
	}
	return 0;
}

H - Tree Cutting (Easy Version)

CodeForces - 1118F1
题意:给定一棵树,结点有红色、蓝色或着无色,删掉一条边,使得剩下两部分,对每个部分,颜色恰好都是同一种颜色
题解:树形dp,red、blue代表树的总红色数、总蓝色数,记录b[u]记录u以及u的子树已有蓝色数,r[u]记录u以及u的子树已有红色数。
如果b[u]==blue&&r[u]==0,或者b[u]==0&&r[u]==red,则ans+1.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=300010;

int n,a[maxn];
int r[maxn],b[maxn];
vector<int> ve[maxn];
bool vis[maxn];
int ans,red,blue;
void dfs(int u)
{
	vis[u]=1;
	r[u]=b[u]=0;
	if(a[u]==1) r[u]++;
	if(a[u]==2) b[u]++;
	for(int i=0;i<ve[u].size();i++){
		int v=ve[u][i];
		if(vis[v]) continue;
		dfs(v);
		r[u]+=r[v];b[u]+=b[v];
	}
	if(r[u]==red&&b[u]==0||b[u]==blue&&r[u]==0) ans++;
}
int main()
{
	while(~scanf("%d",&n)){
		red=blue=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			if(a[i]==1) red++;
			if(a[i]==2) blue++;
			ve[i].clear();
			vis[i]=0;
		}
		int u,v;
		for(int i=1;i<n;i++){
			scanf("%d%d",&u,&v);
			ve[u].push_back(v);
			ve[v].push_back(u);
		}
		ans=0;
		dfs(1);
		printf("%d\n",ans);
	}
	return 0;
}

I - Magic Gems

CodeForces - 1117D
题解转载自:http://www.mamicode.com/info-detail-2619457.html
题意:
给定N,M(n <1e18,m <= 100)
一个magic gem可以分裂成M个普通的gem,现在需要N个gem,可以选择一定的magic gem,指定每一个分裂或不分裂,问一共有多少种方案
两种分裂方案不同当且仅当magic gem的数量不同,或者分裂的magic gem的索引不同。
思路:
1.首先从dp的角度出发
设F(i)为最终需要i个gem的方案数,容易得到递推式:

(总方案数 = 最右边的magic gem分裂得到的方案数 + 最右边的magic gem不分裂得到的方案数)
2.观察数据范围可以看到,如果直接这样计算,时间复杂度是要上天的
我们可以把递推式求解转化成矩阵乘法求解
在这里插入图片描述

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long
const int maxn=100010;
#define inf 0x3f3f3f3f
const int mod=1e9+7;

ll n,m;
struct matrix{
	ll a[110][110];
	matrix(){
		for(int i=1;i<=m;i++)
			for(int j=1;j<=m;j++)
				a[i][j]=0;
	}
};
matrix mul(const matrix& x,const matrix& y)
{
	matrix res;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=m;j++){
			res.a[i][j]=0;
			for(int k=1;k<=m;k++){
				res.a[i][j]+=x.a[i][k]*y.a[k][j];
				res.a[i][j]%=mod;
			}
		}
	}
	return res;
}
matrix quickpower(matrix a,ll b)
{
	matrix res;
	for(int i=1;i<=m;i++)
		res.a[i][i]=1;
	while(b){
		if(b&1) res=mul(res,a);
		b>>=1;
		a=mul(a,a);
	}
	return res;
}
int main()
{
	while(~scanf("%lld%lld",&n,&m)){
		if(n<m){
			printf("1\n");
			continue;
		}
		matrix ans,x;
		for(int i=1;i<m;i++)
			ans.a[i+1][i]=1;
		ans.a[1][m]=ans.a[m][m]=1;
		ans=quickpower(ans,n-m);
		for(int i=1;i<m;i++)
			x.a[1][i]=1;
		x.a[1][m]=2;
		ans=mul(x,ans);
		printf("%lld\n",ans.a[1][m]);
	}
	return 0;
}


J - The Fair Nut and the Best Path

CodeForces - 1083A
参考题解:https://blog.csdn.net/ZscDst/article/details/84977135
先输入N表示一棵有N个结点的树,然后输入N个点的点权,然后输入M条边。要求一条链,使得链上的
点权减去链上的边权值最大。
树形dp,状态转移方程:
a n s = m a x ( a n s , d p [ u ] + d p [ v ] − w ) ; d p [ u ] = m a x ( d p [ u ] , a [ u ] + d p [ v ] − w ) ; ans=max(ans,dp[u]+dp[v]-w);dp[u]=max(dp[u],a[u]+dp[v]-w); ans=max(ans,dp[u]+dp[v]w);dp[u]=max(dp[u],a[u]+dp[v]w);

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define ll long long
const int maxn=300010;
#define inf 0x3f3f3f3f
const int mod=1e9+7;

int n,a[maxn];
ll dp[maxn];
struct edge{
	int v,w;
}cur;
vector<edge> ve[maxn];
bool vis[maxn];
ll ans;
void dfs(int u)
{
	ans=max(ans,dp[u]);
	vis[u]=1;
	for(int i=0;i<ve[u].size();i++){
		cur=ve[u][i];
		int v=cur.v,w=cur.w;
		if(vis[v]) continue;
		vis[v]=1;
		dfs(v);
		ans=max(ans,dp[u]+dp[v]-w);
		dp[u]=max(dp[u],a[u]+dp[v]-w);
	}
	ans=max(dp[u],ans);
} 
int main()
{
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			dp[i]=1LL*a[i];
			ve[i].clear();
			vis[i]=0;
		}
		int u,v,w;
		for(int i=1;i<n;i++){
			scanf("%d%d%d",&u,&v,&w);
			edge tmp;
			tmp.v=v;tmp.w=w;
			ve[u].push_back(tmp);
			tmp.v=u;
			ve[v].push_back(tmp);
		}
		ans=0;
		dfs(1);
		printf("%lld\n",ans);
	}
}

K - Similar Arrays

CodeForces - 1090D

题解转载自:https://www.cnblogs.com/dilthey/p/10092161.html
题意:
给定 n,m,给定 m 个无序对 (a,b) 代表位置 a 上的数字和位置 b 上的数字进行比较。且这 m 个无序对无重复。
让你寻找两个序列,第一个序列由 1∼n 组成,元素互不相同且唯一。第二个序列,要满足和第一个序列对于 m 个比较的结果相同,且某一个数字要出现两次,其余则皆属于 [1,n]且互不相同且唯一。
题解:
可以想见,如果有 n ∗ ( n − 1 ) 2 \frac{n*(n-1)}{2} 2n(n1)次比较,那么就可以唯一确定该序列,则要输出 “NO” 。
一旦少那么一次,就代表有两个位置上的数字没有比较,不妨令这两个位置上的数字原本是最大和次大,现在全变成最大,这样不会改变 m 次比较的结果。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=1e5+10;
int n,m;
pii p[maxn];
pii findpos()
{
    int a,b,c=0;
    pii res;
    for(a=1;a<=n;a++) for(b=a+1;b<=n;b++) if((res=make_pair(a,b))!=p[++c]) return res;
}
int main()
{
    cin>>n>>m;
    for(int i=1,a,b;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        p[i]=make_pair(min(a,b),max(a,b));
    }
    if((ll)n*(n-1)/2 <= (ll)m) {printf("NO\n");return 0;}

    printf("YES\n");
    sort(p+1,p+1+m);
    pii pos=findpos();
    for(int i=1,cnt=0;i<=n;i++)
    {
        if(i==pos.first) printf("%d ",n-1);
        else if(i==pos.second) printf("%d ",n);
        else printf("%d ",++cnt);
    }
    printf("\n");
    for(int i=1,cnt=0;i<=n;i++)
    {
        if(i==pos.first) printf("%d ",n);
        else if(i==pos.second) printf("%d ",n);
        else printf("%d ",++cnt);
    }
    printf("\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值