蒟蒻补完整的第一场cf QAQ
A题 题目链接(模拟+签到)
题意:给你一个长度为N个序列,你可以每次从最左边或者最右边删除一个小于等于k的数字,问你最多能删多少个数字。 数据N,K<=100
直接上代码,不用解释。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL INF=1e15;
const double eps=1e-5;
const int maxn=6e3+10;
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
int n,k;
int ans=0;
int a[105];
scanf("%d%d",&n,&k);
int p1=1,p2=n;
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
}
while(p1<=p2)
{
if(a[p1]<=k)
{
p1++;
ans++;
}
else
{
if(a[p2]<=k)
{
p2--;
ans++;
}
else break;
}
}
printf("%d",ans);
// system("pause");
return 0;
}
B题 题目链接 (模拟+签到)
题目大意:给定一个字符串,长度为D,我们取D的每个因数(包括自身),从大到小开始,反转字符串的1~d位置,比如D=10, s=“codeforces” ,d分别取 10 5 2 1
“codeforces” → “secrofedoc” → “orcesfedoc” → “rocesfedoc” → “rocesfedoc”
现在给你一个反转后的字符串,求原串。 D<=100
思路:看数据也是不难,直接用栈存D的因数,然后模拟reverse即可
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=1e6+50;
char s[105];
int n;
stack<int> d;
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d %s",&n,s+1);
for(int i=n;i>=2;i--)
{
if(n%i==0) d.push(i);
}
while(!d.empty())
{
int x=d.top();
d.pop();
reverse(s+1,s+1+x);
}
printf("%s",s+1);
// system("pause");
return 0;
}
C题 题目链接 (模拟+签到)
题目大意:给定一个小写字母组成的字符串,一个数k,要求按字典序优先级删去k个字符,并且优先删去左边的(a删完了删b,b删完了删c…)。 求删完后的字符串。
思路:拿数组存一下各字母的个数,然后按abcd…顺序遍历,删除即可
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=4e5+50;
char s[maxn];
char ans[maxn];
int cnt;
int n,k;
int book[128];
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d%d%s",&n,&k,s+1);
for(int i=1;i<=n;i++)
{
book[s[i]]++;
}
for(int i='a';i<='z';i++)
{
if(book[i]>=k)//k耗尽了
{
book[i]-=k;
k=0;
break;
}
else
{
k-=book[i];
book[i]=0;
}
}
for(int i=n;i>=1;i--)//因为优先删左边的,所以从右边开始遍历原串
//如果这个字符还有剩余,就加给ans串,同时数目--
{
if(book[s[i]])
{
book[s[i]]--;
ans[cnt++]=s[i];
}
}
reverse(ans,ans+cnt);//ans串是反的,要反转一下
printf("%s",ans);
// system("pause");
return 0;
}
这场开始40分钟写完了ABC,结束时也只写完了ABC= =、 自闭了1个多小时搞D E题,我太菜了QAQ 。看了题解发现后面三题其实也不难…
D题 题目链接(set+贪心+二分)
这题题意弄清楚了其实不难想qaq
给定n*m个数,每个数可以操作无数次,每次操作可以+1,要求最少操作次数,
满足操作完成后数组元素可以平均分成m组,第i组的所有数%m都为i-1
思路:用set维护没有凑够元素个数的余数
对于每个数a[i] ,设a[i]的余数为t,如果t大于set中任意一个数,就找出set中最小的那个元素x(贪心,因为a[i]只能加1,不能减),
否则从set二分找出第一个大于等于t的余数x,让a[i]%m的余数变成x,操作次数就是(x-t+m)%m。 如果某个余数凑够了n/m个,就把它从set中删了
( 为什么x<t的时候操作次数也是这个?可以设一些数据,草稿演算一下。)
比如m=5,x=0,t=3
0 1 2 3 4 0 1 2 3 4 0 ……
t到x的最短距离就x+m-t 了,再对m取模防止出现大于等于m的数
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=2e5+50;
int n,m;
int num[maxn];//余数是i的个数
int k;
LL ans;
set<int> s;
int a[maxn];
//给定n*m个数,每个数可以操作无数次,每次操作可以+1,要求操作最少次数,
//满足可以平均分成m组 第i组的所有数%m都为i-1
//思路:用set维护没有凑够元素个数的余数
//对于每个数a[i] 设a[i]的余数为t,如果t大于set中任意一个数,就找出set中最小的那个(贪心),
//否则从set二分找出第一个大于等于t的余数,让这个余数的元素个数+1 ,够n/m就从set中删了
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d%d",&n,&m);
k=n/m;
for(int i=0;i<m;i++) s.insert(i);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
}
for(int i=1;i<=n;i++)
{
int t=a[i]%m;
int x;
if(t>(*s.rbegin())) x=*s.begin();//比所有的都大,取最小的余数
else x=*s.lower_bound(t);//否则取第一个大于等于它的余数
if(++num[x]==k) s.erase(x);//凑够了n/m个
ans+=(x-t+m)%m;
a[i]+=(x-t+m)%m;
}
cout<<ans<<'\n';
for(int i=1;i<=n;i++) cout<<a[i]<<' ';
// system("pause");
return 0;
}
E题 题目链接(强连通分量+缩点)
题意:给定一个有向图,起始点s,问最少还要修建多少条有向边,才能使得s可以到达其它所有的顶点
这题一开始以为可以dfs跑一下,记录没访问过的点,然后从没访问过的点开始dfs,求出最少从几个没访问过的点开始dfs可以搜完所有没访问过的点,但是没搞出来QAQ
查题解发现自己就是个黛比,这么裸的强连通分量缩点都没看出来
思路:先用tarjan跑一遍,缩点,记录每个分量点的入度,除s所在的分量点之外,入度为0的分量点数就是答案。 (因为对于每个入度不为0的SCC,取进入这个SCC的SCC就好了233
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=5e3+50;
struct
{
int to,next;
}edge[maxn];
int head[maxn],cnt=1;//链式前向星建图
int n,e,s;//s是出发点
stack<int> st;
int belong[maxn];//点i属于哪个强连通分量
vector<int> scc_g[maxn];//存每个SCC包括了哪些顶点
int indu[maxn];//每个强连通分量的出度,用于缩点,成为有向无环图
int step=0;//时间戳
int cont=0;//强连通分量的个数 从1开始
bool instack[maxn];//标记是否在栈中
int DFN[maxn];//顶点i的进栈时间戳、次序号(就是第几个被搜到的),初始化成0表示还未进栈,定了就不会变了
int low[maxn];
void add(int from,int to)
{
edge[cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt++;
}
void tarjan(int x)
{
DFN[x]=low[x]=++step;
st.push(x);
instack[x]=true;
for(int i=head[x];i;i=edge[i].next)
{
int to=edge[i].to;
if(!DFN[to])
{
tarjan(to);
low[x]=min(low[x],low[to]);
}
else if(instack[to])//早已经入过栈且已经出栈了
{
low[x]=min(low[x],DFN[to]);
}
}
if(DFN[x]==low[x])//以x为根的搜索树是强连通分量
{
cont++;
while(1)//疯狂出栈 直到把x也出了
{
int temp=st.top();
st.pop();
instack[temp]=false;
belong[temp]=cont;
scc_g[cont].push_back(temp);
if(temp==x) break;
}
}
}
void getpoint()
{
for(int i=1;i<=n;i++)//链式前向星遍历方法 ,枚举出发点,出发点的每条边
{
for(int j=head[i];j;j=edge[j].next)
{
int to=edge[j].to;
if(belong[i]!=belong[to]) indu[belong[to]]++;
}
}
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d%d%d",&n,&e,&s);
for(int i=1;i<=e;i++)
{
int v,u;
scanf("%d%d",&v,&u);
add(v,u);
}
for(int i=1;i<=n;i++)
{
if(!DFN[i]) tarjan(i);
}
getpoint();//缩点
int ans=0;
indu[belong[s]]++;//保证s所在 入度不为0
for(int i=1;i<=cont;i++)
{
if(!indu[i]) ans++;
}
printf("%d",ans);
// system("pause");
return 0;
}
F题 题目链接 (思维+01背包)
题意:给定n*k个卡牌,每张牌标有数字,n个人每个人都有自己的幸运数字,每人发k张牌,
对于每个人,得到x张幸运数字卡可以获得joy[x]的快乐值 ,且joy数组单调递增,问最大分配价值
(拿到题莫名有种01背包的感觉,但是就是不晓得咋下手orz,果然太菜了。 )
思路:由贪心可知,只有把卡牌分给幸运数字对应的人,才能创造最优价值。
我们记dp[i][j]为 对于某种幸运数字L,i个人分配j个卡牌的最优解(i个人的幸运数字都是L,卡牌数字也都是L,也就是把每个幸运数字分开解决)。
我们不重复地枚举每个幸运数字L,计算dp[i][j] 最终加起来就是答案
这样的话就是个比较好写的01背包
dp[i][j]=max(dp[i][j],dp[i-1][j-k]+joy[k]) (第i个人取k个数字,枚举k即可
先上开二维数组的代码,时间1600ms
当然可以01背包滚动数组优化,省略一维,这样时间是40ms,不懂为啥差别那么大…以后就开一维了
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=5e2+50;
int n,k;
int number[maxn];//各自幸运数字
int joy[maxn];//得到i个 获得的快乐值
int num[100005];//各种数字 各有几张
int stu[100005];//各种数字 都有几个人
//给定n*k个卡牌,标数,n个人每个人都有自己的幸运数字,每人发k张牌,
//得x张幸运数字卡可以获得joy[x]的快乐值 问最大分配价值
int dp[maxn][5005];//第二维是500*10
//由贪心可知应该把所有的卡分给幸运数字与卡片数字相应的人
//dp[i][j] 表示 i个幸运数字相同的人 分j个幸运数字卡 最优解
//dp[i][j]= max(dp[i-1][j-k]+joy[k]) 枚举k
LL ans;
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d%d",&n,&k);
for(int i=1;i<=n*k;i++)
{
int t;
scanf("%d",&t);
num[t]++;
}
for(int i=1;i<=n;i++)
{
scanf("%d",number+i);
stu[number[i]]++;
}
for(int i=1;i<=k;i++) scanf("%d",joy+i);
for(int L=0;L<=100000;L++)
{
if(num[L] && stu[L])//这个幸运数字的卡 和 人 都有
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=stu[L];i++)
{
for(int j=1;j<=num[L];j++)
{
for(int m=0;m<=min(j,k);m++)
{
dp[i][j]=max(dp[i][j],dp[i-1][j-m]+joy[m]);
}
}
}
ans+=dp[stu[L]][num[L]];
}
}
printf("%lld\n",ans);
// system("pause");
return 0;
}
时间1600ms
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=5e2+50;
int n,k;
int joy[maxn];//得到i个 获得的快乐值
int num[100005];//各种数字 各有几张
int stu[100005];//各种数字 都有几个人
//给定n*k个卡牌,标数,n个人每个人都有自己的幸运数字,每人发k张牌,
//得x张幸运数字卡可以获得joy[x]的快乐值 问最大分配价值
int dp[5001];
//由贪心可知应该把所有的卡分给幸运数字与卡片数字相应的人
//dp[i][j] 表示 i个幸运数字相同的人 分j个幸运数字卡 最优解
//dp[i][j]= max(dp[i-1][j-k]+joy[k]) 枚举k
LL ans;
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d%d",&n,&k);
for(int i=1;i<=n*k;i++)
{
int t;
scanf("%d",&t);
num[t]++;
}
for(int i=1;i<=n;i++)
{
int t;
scanf("%d",&t);
stu[t]++;
}
for(int i=1;i<=k;i++) scanf("%d",joy+i);
for(int L=0;L<=100000;L++)
{
if(num[L] && stu[L])//这个数字的卡 和 人 都有
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=stu[L];i++)
{
for(int j=num[L];j;j--)//记得逆序
{
for(int m=0;m<=min(j,k);m++)
{
dp[j]=max(dp[j],dp[j-m]+joy[m]);
}
}
}
ans+=dp[num[L]];
}
}
printf("%lld\n",ans);
// system("pause");
return 0;
}
至此,这场cf的题算是全部补完了,在感慨自己太菜之余还是学到了一些东西的。