前言
A. Minimum Integer
题意
给出q次询问,每次询问给出l,r,d,问不在 [ l , r ] [l,r] [l,r]内的d的最小倍数是多少。
做法
1:
l
>
d
−
>
d
l>d ->d
l>d−>d
2:
(
r
d
+
1
)
×
d
\left( \frac{r}{d}+1 \right) \times d
(dr+1)×d
代码
#include<stdio.h>
typedef long long ll;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
ll a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
if(a>c) printf("%lld\n",c);
else printf("%lld\n",1LL*(b/c+1)*c);
}
return 0;
}
B. Accordion
题意
定义手风琴的样子为"[:"+k*"|"+":]"
k可以是任何自然数。
问给定字符串通过删除某些字符之后可以得到的最长手风琴的长度
做法
从左往右找到第一个"[“之后的”:",找不到[或者找不到:,返回-1
从右往左找到第一个"]“之前的”:",找不到[或者找不到:或者找到的:在之前找到的:左边,返回-1
两个:重合时也要返回-1.最后答案就是4+两个冒号中间的|的个数
代码
#include<stdio.h>
#include<string.h>
const int maxn = 1e6+5;
char str[maxn];
int main()
{
scanf("%s",str);
int len=strlen(str);
if(len<4)
{
printf("-1\n");
return 0;
}
int tt=0;
int cnt=0;
int pos1=-1,pos2=-1;
for(int i=0;i<len;i++)
{
if(tt==0&&str[i]=='[')
{
tt++;
cnt++;
}
else if(tt==1&&str[i]==':')
{
tt++;
cnt++;
pos1=i;
break;
}
}
if(pos1==-1)
{
printf("-1\n");
return 0;
}
tt=0;
for(int i=len-1;i>=0;i--)
{
if(tt==0&&str[i]==']')
{
tt++;
cnt++;
}
else if(tt==1&&str[i]==':')
{
tt++;
cnt++;
pos2=i;
break;
}
}
if(tt==0||pos1>=pos2)
{
printf("-1\n");
return 0;
}
int ans=4;
for(int i=pos1+1;i<=pos2-1;i++)
{
if(str[i]=='|') ans++;
}
printf("%d\n",ans);
return 0;
}
C. Division and Union
题意
给你n条线段,问能不能把线段分成两个集合,保证来自不同集合的线段没有交点。
做法
只要把第一段出现空隙的线段拿出来放到第一个集合
其余线段放到第二个集合就可以了。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e5+5;
struct data
{
int l,r;
int id;
}L[maxn];
bool cmp(const data &a,const data &b)
{
if(a.l==b.l) return a.r<b.r;
return a.l<b.l;
}
int ans[maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&L[i].l,&L[i].r);
L[i].id=i;
ans[i]=1;
}
sort(L+1,L+1+n,cmp);
int maxx=L[1].r;
int cnt=1;
int flag=0;
for(int i=2;i<=n;i++)
{
if(L[i].l>maxx)
{
flag=1;
for(int j=i-1;j>=i-1-cnt+1;j--)
{
ans[L[j].id]=2;
}
break;
}
else
{
cnt++;
maxx=max(maxx,L[i].r);
}
}
if(flag==0)
{
printf("-1\n");
continue;
}
for(int i=1;i<=n;i++)
{
printf("%d",ans[i]);
if(i==n) printf("\n");
else printf(" ");
}
}
return 0;
}
题意
给你一个n
个点的带权树,第i
个点的权值为a[i]
求一条树上最长的路径,满足路径上所有点权的gcd
不为1
。
1
<
=
n
<
=
2
∗
1
0
5
1<=n<=2*10^5
1<=n<=2∗105
1
<
=
a
i
<
=
2
∗
1
0
5
1<=a_i<=2*10^5
1<=ai<=2∗105
做法
首先要想到,gcd
是质因子就足够了,也就是说,肯定存在一条最长路径公约数是质数。
之后对于一个小于
2
∗
1
0
5
2*10^5
2∗105的数,他最多存在7
个质因子
这里是因为,
2
∗
3
∗
5
∗
7
∗
11
∗
13
∗
17
=
510510
>
2
∗
1
0
5
2*3*5*7*11*13*17=510510>2*10^5
2∗3∗5∗7∗11∗13∗17=510510>2∗105
所以对于树上的每个点,只需要求这些质因子最多能向下延伸的长度。
我们就dp[i][j]
为以i为起点的向i的子树延伸的gcd
为a[i]
的第j个质因子的最长链的长度。
那么每次更新dp[i][j]
一定是他某个儿子也含有这个质因子,我们想要更新dp[i][j]
就要知道这个质因子是他儿子的第几个质因子,
所以我们预处理的时候用map
存储,map[pair<i,j>]=k
表示i
在j
中是第k
个质因子。
存储质因子用vector
存储,G[i][j]
表示i的第j
个质因子是哪个。
这样我们就可以得到更新dp[i][j]
的转移方程
设当前访问的结点为rt
,要转移的质因子为第j
个,当前访问的儿子为to
if(a[to]%G[rt][j]==0)
int pos=mp[pii(G[rt][j],to)];//rt的第j个质因子是to的第pos个质因子
dp[rt][j]=max(dp[rt][j],dp[to][pos]+1);
类似dp
求树的直径,我们对所有儿子中的可更新父亲第j
个因子的dp
值取前两大
也就是以rt
为根的子树内,gcd
为a[rt]
第j
个质因子的最长路径。
最后对每个子树内每个质因子能达到的最长路径取max
就是答案。
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
typedef pair <int, int> pii;
const int maxn = 2e5+5;
int a[maxn];
int dp[maxn][10];
vector<int> G[maxn];
vector<int> zhuan[maxn];
vector<int> vec[maxn];
map<pii,int> mp;
int ans=0;
void dfs(int rt,int fa)
{
for(int i=0;i<G[rt].size();i++) dp[rt][i]=1;
int maxx1[10],maxx2[10];
for(int i=0;i<10;i++)
{
maxx1[i]=0;
maxx2[i]=0;
}
for(int i=0;i<vec[rt].size();i++)
{
int to=vec[rt][i];
if(to==fa) continue;
dfs(to,rt);
for(int j=0;j<G[rt].size();j++)
{
if(a[to]%G[rt][j]==0)
{
int pos=mp[pii(G[rt][j],to)];//rt的第j个质因子是to的第pos个质因子
int tmp=dp[to][pos];
if(maxx1[j]<tmp)
{
maxx2[j]=maxx1[j];
maxx1[j]=tmp;
}
else if(maxx2[j]<tmp)
{
maxx2[j]=tmp;
}
}
}
}
for(int i=0;i<G[rt].size();i++)
{
dp[rt][i]+=maxx1[i];
ans=max(ans,maxx1[i]+maxx2[i]+1);
}
return ;
}
int main()
{
int n,u,v;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
int k=a[i];
for(int j=2;j*j<=k;j++)
{
if(k%j==0)
{
G[i].push_back(j);
mp[pii(j,i)]=G[i].size()-1;
while(k%j==0) k/=j;
}
}
if(k>1)
{
G[i].push_back(k);
mp[pii(k,i)]=G[i].size()-1;
}
}
for(int i=1;i<=n-1;i++)
{
scanf("%d%d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs(1,-1);
printf("%d\n",ans);
return 0;
}
E. Polycarp’s New Job
题意
一共有q次操作,每次操作有两种类型,
第一种类型+ x y
,增加一个x*y
的长方形
第二种类型? h w
,当前所有长方形可以叠加,是否可以用一个h*w
的长方形盖住之前左右长方形
做法
做法就是,维护一个最小需要的长方形的长宽。
长maxh是所有加进来的长方形的长的max
宽maxw是所有加进来的长方形的宽的max
之后对于每个要检查的长方形,只要maxh<=max(h,w)&&maxw<=min(h,w)就是YES否则是NO
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int n;
scanf("%d",&n);
int a[2];
a[0]=0;
a[1]=0;
while(n--)
{
char op[2];
int x[2];
scanf("%s%d%d",op,&x[0],&x[1]);
sort(x,x+2);
sort(a,a+2);
if(op[0]=='+')
{
a[0]=max(a[0],x[0]);
a[1]=max(a[1],x[1]);
}
else
{
if(a[0]<=x[0]&&a[1]<=x[1])
{
puts("YES");
}
else
{
puts("NO");
}
}
}
return 0;
}
F. Ivan and Burgers
题意
有n个城市在x轴上,有m辆卡车,每辆卡车有四个属性,分别是起始城市s,终止城市f,每公里消耗燃料燃料消耗c,和可加油次数r。每次加油卡车油量加满,卡车的油量为V,所有卡车初始油量都是满的。求能让所有卡车从起点到达终点的最小油量V。
2
<
=
n
<
=
400
,
1
<
=
m
<
=
250000
2<=n<=400,1<=m<=250000
2<=n<=400,1<=m<=250000
1
<
=
a
i
<
=
1
0
9
,
a
i
<
a
i
+
1
1<=a_i<=10^9,a_i<a_{i+1}
1<=ai<=109,ai<ai+1
做法
首先考虑暴力的
n
4
n^4
n4的做法。设
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]为 从
i
i
i出发到达
j
j
j休息
k
k
k次的最小间隔。
d
p
[
i
]
[
j
]
[
k
]
=
min
j
−
1
l
=
i
+
1
(
max
(
d
p
[
i
]
[
l
]
[
k
−
1
]
,
a
[
j
]
−
a
[
l
]
)
)
dp\left[ i \right] \left[ j \right] \left[ k \right] =\underset{l=i+1}{\overset{j-1}{\min}}\left( \max \left( dp\left[ i \right] \left[ l \right] \left[ k-1 \right] ,a\left[ j \right] -a\left[ l \right] \right) \right)
dp[i][j][k]=l=i+1minj−1(max(dp[i][l][k−1],a[j]−a[l]))
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 405;
int dp[maxn][maxn][maxn]; // dp[i][j][k] 表示从i出发到达j中间停留k次的最长间隔。
int a[maxn];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
dp[i][j][0]=a[j]-a[i];
for(int k=1;k<=n;k++)
{
dp[i][j][k]=a[j]-a[i];
for(int l=i+1;l<j;l++)
{
dp[i][j][k]=min(dp[i][j][k],max(dp[i][l][k-1],a[j]-a[l]));
}
}
}
}
ll ans=0;
for(int i=1;i<=m;i++)
{
int s,f,c,r;
scanf("%d%d%d%d",&s,&f,&c,&r);
ans=max(ans,1LL*dp[s][f][r]*c);
}
printf("%lld\n",ans);
return 0;
}
首先我们可以滚动掉第一维,之后我们可以发现,
max
(
d
p
[
i
]
[
l
]
[
k
−
1
]
,
a
[
j
]
−
a
[
l
]
)
\max \left( dp\left[ i \right] \left[ l \right] \left[ k-1 \right] ,a\left[ j \right] -a\left[ l \right] \right)
max(dp[i][l][k−1],a[j]−a[l])
这个是满足决策单调性的,我们只需要一个单调队列维护,复杂度就变为
n
3
n^3
n3
代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 405;
int dp[maxn][maxn]; // dp[i][j][k] 表示从i出发到达j中间停留k次的最长间隔。
int a[maxn];
int pos[maxn];
struct data
{
int f,c,r;
data(){}
data(int ff,int cc,int rr)
{
f=ff;
c=cc;
r=rr;
}
};
vector<data> G[maxn];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
int s,f,c,r;
scanf("%d%d%d%d",&s,&f,&c,&r);
G[s].push_back(data(f,c,r));//离线处理每个起点为s的查询
}
ll ans=0;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
dp[j][0]=a[j]-a[i];
int pos=0;
for(int k=1;k<=n;k++)
{
dp[j][k]=a[j]-a[i];
while(pos+1<=j&&dp[pos+1][k-1]<a[j]-a[pos+1]) pos++;//单调队列优化DP
dp[j][k]=min(dp[j][k],min(a[j]-a[pos],dp[pos+1][k-1]));
}
}
for(int j=0;j<G[i].size();j++)
{
int f=G[i][j].f;
int c=G[i][j].c;
int r=G[i][j].r;
ans=max(ans,1LL*dp[f][r]*c);
}
}
printf("%lld\n",ans);
return 0;
}
G. (Zero XOR Subset)-less
题意
题意就是给你一个长度为n的序列每个数的大小为a[i]
要求把序列分为多个连续的段,保证分完之后,
无论选取那些段相异或答案都不是0,问最多可以分为多少段。
1
<
=
n
<
=
2
∗
1
0
5
1<=n<=2*10^5
1<=n<=2∗105
0
<
=
a
i
<
=
1
0
9
0<=a_i<=10^9
0<=ai<=109
做法
首先由于答案要求是连续的段,而每一段的异或和是一个值,这个值刚好可以通过两个前缀异或和异或值得到,那么每一段的值就转换为前缀异或和,那么问题就变成,给你n个值,让你在其中选出尽量多的值,保证这些值任意组合异或和都不为0.这就变成线性基的经典问题。直接构造出n个前缀异或和的线性基,基底的个数便是答案。
代码
#include<stdio.h>
int p[65];
int main()
{
int n,x,now=0;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&x);
now=now^x;
x=now;
for(int j=31;j>=0;j--)
{
if(x&(1<<j))
{
if(!p[j])
{
p[j]=x;
break;
}
else x=x^p[j];
}
}
}
if(now==0)
{
printf("-1\n");
return 0;
}
int ans=0;
for(int i=31;i>=0;i--) if(p[i]) ans++;
printf("%d\n",ans);
return 0;
}