A. Fair Game
题目链接
题意:给你 n n n个数,问能否将 n n n个数分成两组,每一组里的数完全相同,且两个组包含的数的数目相同。
思路:模拟即可。
代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 200 + 10;
int vis[A],val[A];
int main(){
int tot = 0;
int n;scanf("%d",&n);
for(int i=1 ;i<=n ;i++){
int x;scanf("%d",&x);
if(!vis[x]){
val[++tot] = x;
}
vis[x]++;
}
if(tot == 2 && vis[val[1]] == vis[val[2]]){
puts("YES");
printf("%d %d\n",val[1],val[2]);
}
else puts("NO");
return 0;
}
B. Polycarp and Letters
题目链接
题意:
给你一个字符串,求某些特殊的位置
A
A
A满足:
1.
A
A
A位置的字符为小写字母且任意两个不同的
A
A
A位置的小写字母不同。
2.将所有
A
A
A位置从左到右排序后,任意相邻两个
A
A
A位置之间不含大写字母。
问
A
A
A位置最多能有多少?
思路:
由条件
2
2
2,可以以大写字母为界限将字符串分成若干个子串,对于每个子串单独计算答案。
考虑遍历每个子串,看其中有多少种不同的小写字母出现。
所有子串的答案取一个
m
a
x
max
max即可。
代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 200 + 10;
char s[A];
bool vis[A];
int calc(int S,int T){
int res = 0;
memset(vis,0,sizeof(vis));
for(int i=S ;i<=T ;i++){
if(!vis[s[i]-'a']) res++;
vis[s[i]-'a'] = 1;
}
return res;
}
int main(){
int n;
scanf("%d",&n);
scanf("%s",s);
int ans = 0;
for(int i=0 ;i<n ;){
if(s[i]>='A' && s[i]<='Z'){
i++;
continue;
}
int j;
for(j=i+1 ;j<n ;j++){
if(s[j]>='A' && s[j]<='Z'){
break;
}
}
ans = max(ans,calc(i,j-1));
i = j+1;
}
printf("%d\n",ans);
return 0;
}
C. Bus
题目链接
题意:
一辆车行驶在一条直线上,起点坐标为
0
0
0,终点坐标为
a
a
a。
汽车从起点到终点,或者从终点到起点称为一次旅途。
汽车每行驶一个单位会消耗一升汽油,一开始汽车有
b
b
b升汽油。
在坐标
f
f
f处有一个加油站,汽车每次经过加油站可以选择路过或者把油加满。
问汽车从起点出发,完成 k k k次旅途,最少需要加几次油。
思路:
开始分类讨论了很久,虽然存在很多重复状态,但思路并没有真正理清楚。
后来考虑到
k
k
k很小,便直接模拟,每当汽车经过加油站时,判断一下当前的油量能否支撑它下一次回到加油站。
贪心即可。
注意特判开始和最后的状态。
代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int main(){
int a,b,f,k;
scanf("%d%d%d%d",&a,&b,&f,&k);
if(b<f){
puts("-1");
return 0;
}
int now = b - f;
int ans = 0;
for(int i=1 ;i<k ;i++){
int need = i&1?2*(a-f):2*f;
if(need > b){
puts("-1");
return 0;
}
if(need>now){
ans++;
now = b - need;
}
else{
now -= need;
}
}
int need = k&1?(a-f):f;
if(need>b){
puts("-1");return 0;
}
if(need>now) ans++;
printf("%d\n",ans);
return 0;
}
D. Make a Permutation!
题目链接
题意:
有
n
n
n个数,每个数的值
(
1
<
=
v
a
l
<
=
n
)
(1<=val<=n)
(1<=val<=n),现在能改变任意一个元素为任意值,问最少需要改变多少个数,使改变后这
n
n
n个数为
1
1
1~
n
n
n的一个排列。
若有多种改变方式,则输出改变之后字典序最小的排列。
思路:
很明显,如果某个数出现了
c
n
t
cnt
cnt次(cnt>1),则需要将剩下的
c
n
t
−
1
cnt-1
cnt−1个数改变成没有出现的数。
另外,若有多个没有出现的数,则需要改变的位置越靠前就越应该贪心地填入小的数。
考虑使用优先队列来模拟上面的过程。
另外注意判断一下,若某个数出现了 c n t cnt cnt次,则需要改变的那 c n t − 1 cnt-1 cnt−1个位置是任意的,那一个位置不改变需要和队列顶元素进行判断。
代码:
#include<cmath>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 2e5 + 10;
int vis[A],a[A];
bool Bi[A];
priority_queue<int> que;
int main(){
int n;scanf("%d",&n);
for(int i=1 ;i<=n ;i++){
scanf("%d",&a[i]);
vis[a[i]]++;
}
int tot = 0;
for(int i=1 ;i<=n ;i++){
if(!vis[i]) que.push(-i);
}
for(int i=1 ;i<=n ;i++){
if(vis[a[i]]>1){
int u = -que.top();
if(!Bi[a[i]] && a[i]<u){
Bi[a[i]] = 1;
continue;
}
vis[a[i]]--;
que.pop();
a[i] = u;
tot++;
}
}
printf("%d\n",tot);
for(int i=1 ;i<=n ;i++){
printf("%d%c",a[i],i==n?'\n':' ');
}
return 0;
}
E. Fire
题目链接
题意:
一场火灾中,有
n
n
n件物品,取出它需要花费
t
i
t_i
ti的时间,然后能够得到
v
i
v_i
vi的价值,但每件物品一旦时间超过了
d
i
d_i
di则彻底损坏。
问能取得的最大价值是多少,并输出取的策略。
思路:
考虑对时间进行DP。
如果存在最大价值的方案,由贪心的思想,其中彻底损坏时间最小的一定可以最先取。
将物品按损坏时间排序后,就变成了经典的01背包问题。
记录一下DP路径然后输出即可。
代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 2e3 + 10;
int dp[A],pre[A];
class P{
public:
int t,d,p,id;
bool operator<(const P& rhs) const{
return d < rhs.d;
}
}a[110];
int tot,Ans[110];
bool vis[A][A];
int main(){
//freopen("output","w",stdout);
int n;scanf("%d",&n);
for(int i=1 ;i<=n ;i++){
scanf("%d%d%d",&a[i].t,&a[i].d,&a[i].p);
a[i].id = i;
}
sort(a+1,a+n+1);
for(int i=1 ;i<=n ;i++){
for(int j=a[i].d ;j-a[i].t>=1 ;j--){
if(dp[j-a[i].t] + a[i].p > dp[j]){
dp[j] = dp[j-a[i].t] + a[i].p;
vis[i][j] = 1;
}
}
}
int Mx = 0,now = 0;
for(int i=0 ;i<A ;i++){
if(dp[i]>=Mx){
Mx = dp[i];
now = i;
}
}
tot = 0;
for(int i=n ;i>=1 ;i--){
if(vis[i][now]){
Ans[++tot] = a[i].id;
now -= a[i].t;
}
}
printf("%d\n",Mx);
printf("%d\n",tot);
for(int i=tot ;i>=1 ;i--){
printf("%d%c",Ans[i],i==1?'\n':' ');
}
return 0;
}
F. Cities Excursions
题目链接
题意:
n
n
n个点,
m
m
m条边的有向图,给出
q
q
q个询问
s
,
t
,
k
s,t,k
s,t,k,
问从
s
s
s到
t
t
t的所有路径中字典序最小的那条路径上第
k
k
k个点是多少。
思路:
(参考官网题解)
q q q很大而 n n n很小,故很多询问一定有重复的 s s s或者 t t t,考虑离线打包一起处理。
遍历每一个 t t t,可以反向建图后 d f s dfs dfs,得出所有合法的,即从 s s s到 t t t存在可达路径的 s s s。
因为每个 s s s都是按照最优策略,即可选路径中字典序最小的路径走,故所有合法的 s s s和 t t t构成了一棵以 t t t为根的生成树,然后从 s s s到 t t t的第 k k k个节点,就成了求 s s s节点在这棵树上的第 k k k层祖先。
考虑倍增优化该过程。
代码:
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int A = 3e3 + 10;
const int B = 4e5 + 10;
class Gra{
public:
int v,next;
}G[A];
int head[A],tot;
class Qu{
public:
int v,next,k,id;
}Q[B];
int Q_head[B],Q_tot,dp[A][15],Ans[B];
int n,m,q;
void init(){
memset(head,-1,sizeof(head));
memset(Q_head,-1,sizeof(Q_head));
}
void add_G(int u,int v){
G[tot].v = v;
G[tot].next = head[u];
head[u] = tot++;
}
void add_Q(int u,int v,int k,int id){
Q[Q_tot].v = v;
Q[Q_tot].id = id;
Q[Q_tot].k = k;
Q[Q_tot].next = Q_head[u];
Q_head[u] = Q_tot++;
}
void dfs(int u,int tp){
for(int i=head[u] ;i!=-1 ;i=G[i].next){
int v = G[i].v;
if(v!=tp && (!dp[v][0] || dp[v][0]>u)){
dp[v][0] = u;
dfs(v,tp);
}
}
}
int main(){
init();
scanf("%d%d%d",&n,&m,&q);
for(int i=1 ;i<=m ;i++){
int u,v;scanf("%d%d",&u,&v);
add_G(v,u);
}
for(int i=1 ;i<=q ;i++){
int u,v,k;
scanf("%d%d%d",&u,&v,&k);
add_Q(v,u,k,i);
}
int Mx = (int)(log(1.0*n)/log(2.0)) + 2;
for(int i=1 ;i<=n ;i++){
memset(dp,0,sizeof(dp));
dfs(i,i);
for(int k=1 ;k<=Mx ;k++){
for(int j=1 ;j<=n ;j++){
dp[j][k] = dp[dp[j][k-1]][k-1];
}
}
for(int j=Q_head[i] ;j!=-1 ;j=Q[j].next){
int u = Q[j].v,k = Q[j].k-1,id = Q[j].id;
if(!dp[u][0] || dp[u][Mx]){
Ans[id] = -1;continue;
}
int dep = 0;
while(k){
if(k&1) u = dp[u][dep];
k >>= 1;
dep++;
}
Ans[id] = u?u:-1;
}
}
for(int i=1 ;i<=q ;i++){
printf("%d\n",Ans[i]);
}
return 0;
}