题目链接: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)
(n∗k)=L(%L)那么移动次数=
l
c
m
(
n
∗
k
,
l
)
/
l
=
n
∗
k
/
g
c
d
(
n
k
,
l
)
,
n
<
=
1
e
5
lcm(n*k,l)/l=n*k/gcd(nk,l),n<=1e5
lcm(n∗k,l)/l=n∗k/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∗(n−1)次比较,那么就可以唯一确定该序列,则要输出 “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");
}