ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
题目链接:https://codeforces.com/contest/1187
A
题意: 有n个蛋,蛋里放着物品A,或者物品B,或者物品A和B,A有s个,B有t个,问至少拿多少个蛋才能拿到A和B。
解法:模拟~画一条线,长度为n,A从左边开始,B从右边开始,中间是重叠部分,左右两边是不重叠部分,取大的一边,再加一就是答案了…
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int T;
int main(){
cin>>T;
while(T--){
ll n,s,t;
cin>>n>>s>>t;
ll ans = max(n-s,n-t)+1;
cout<<ans<<endl;
}
return 0;
}
B
题意:有一个字符串s,有N个字符串t,问要包含字符串t的所有字母,至少去字符串s的前缀多长。
解法:统计字符串s的信息,cnt[‘a’][x]=num:表示a出现x次在位置num.如cnt[‘a’][1000]=1005表示a的第1000次 出现在位置1005
对于每个t,统计它的字母出现的次数。然后根据这些次数在cnt里面查找答案。
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=2e5+5;
int n,m,cnt[33][N],cnt1[33],cnt2[33];
char s[N],t[N];
void init(){
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1; i<=n; i++){
cnt[s[i]-'a'][++cnt1[s[i]-'a']]=i; // s[i]出现cnt1次在位置i
}
}
int main(){
init();
scanf("%d",&m);
while(m--){
scanf("%s",t+1);
int len = strlen(t+1);
int ans = 0;
memset(cnt2,0,sizeof(cnt2));
for(int i=1; i<=len; ++i){
cnt2[t[i]-'a']++;
}
for(int i=0; i<26; i++){
ans=max(ans, cnt[i][cnt2[i]]);
}
cout<<ans<<endl;
}
return 0;
}
C
题目:有一个长度为n的未知数列A,这里有m条信息,包含三个信息(t,l,r)
当t=1,表示A[l,r]是上升数列
当t=0,表示A[l,r]是非上升数列,也就是这是有一对相邻的对前者大于后者
如果数列存在,输出数列,否则输出NO
解法:n比较小,只有1000。
对于t=1来说,这是必须上升的,所以我们可以先把t=1的情况处理了,把上升的位置全都标记出来s[i]=1表示A[i]<A[i-1],数据比较小,直接暴力标记了,也可以用差分,最后再一次性标记
对于t=0来说,我们只有能找到一个s[i]!=1的就行 l < = i < r l<=i<r l<=i<r,否则就无法构造数列
然后构造数列
非暴力法,就是差分之后确定了上升序列的位置后,然后构造数列
接着使用t=0的来进行验证
code略微暴力
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=1e3+5;
int n,m,s[N],ans[N];
struct node{
int t,l,r;
bool operator < (const node &p)const{
return t>p.t;
}
}a[N];
int main(){
cin>>n>>m;
fo(i,1,m){
int t,l,r;
cin>>a[i].t>>a[i].l>>a[i].r;
}
sort(a+1,a+m+1);
fo(i,1,m){
if(a[i].t==1){
for(int j=a[i].l; j<a[i].r; j++){
s[j]=1; // 上升
}
}else{
bool flag = 0;
for(int j=a[i].l; j<a[i].r; j++){
if(s[j]!=1){ // 只要有非上升就标记为下降
s[j]=2;
flag = 1;
break;
}
}
if(!flag){
cout<<"NO"<<endl;
return 0;
}
}
}
ans[1]=1005;
for(int i=2; i<=n; i++){
if(s[i-1]==2){
ans[i]=ans[i-1]-1;
} else{
ans[i]=ans[i-1]+1;
}
}
cout<<"YES"<<endl;
fo(i,1,n){
cout<<ans[i]<<" ";
}
return 0;
}
code差分优化
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=1e3+5;
int n,m,s[N],ans[N],d[N];
struct node{
int t,l,r;
bool operator < (const node &p)const{
return t>p.t;
}
}a[N];
int main(){
cin>>n>>m;
fo(i,1,m){
int t,l,r;
cin>>a[i].t>>a[i].l>>a[i].r;
}
sort(a+1,a+m+1);
fo(i,1,m){
if(a[i].t==1){
d[a[i].l] += 1;
d[a[i].r] -= 1;
}
}
fo(i,1,n){
d[i] += d[i-1];
}
// 构造答案
ans[1]=n;
for(int i=2; i<=n; i++){
if(d[i-1]>0){
ans[i]=ans[i-1]+1;
}else{
ans[i]=ans[i-1]-1;
}
}
// 检查答案
fo(i,1,m){
if(a[i].t==0){
if(ans[a[i].r]-ans[a[i].l] == a[i].r-a[i].l){
// 完全上升
cout<<"NO"<<endl;
return 0;
}
}
}
cout<<"YES"<<endl;
fo(i,1,n){
cout<<ans[i]<<" ";
}
return 0;
}
E
题意:有一个无向图(树),第一次选择一个点作为根,
(之后选择的点是已经选过的点的子节点)每选择一个点,对答案的贡献为树的大小
问:怎么选使得答案最大
解法:
暴力:n遍dfs统计出来(T)
优化:二次扫描和换根法
二次换根法简单介绍:先一次统计出确定了一个根的答案(由所有子节点的答案加自己的和),换根:把新根的答案(在旧根里是子儿子,在新根里它已经成为一棵树的根了)去掉,然后加上旧根的答案(旧根原来是一刻树的根,现在变成子节点了)
code
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=2e5+5,M=N*2;
int n;
int head[N],Next[M],ver[M],tot;
void add(int x, int y){
ver[++tot]=y;
Next[tot]=head[x], head[x]=tot;
}
int son[N];
ll ans;
void dfs(int x, int fa){
son[x]=1;
for(int i=head[x]; i; i=Next[i]){
int y=ver[i];
if(y==fa)continue;
dfs(y,x);
son[x]+=son[y];
}
ans+=son[x]; // 每个选择的点对答案的贡献
}
void DFS(int x, int fa, ll now){
ans = max(ans, now);
for(int i=head[x]; i; i=Next[i]){
int y = ver[i];
if(y==fa)continue;
DFS(y, x, now-son[y]+(n-son[y]));
}
}
int main(){
cin>>n;
int x,y;
fo(i,2,n){
cin>>x>>y;
add(x,y);
add(y,x);
}
dfs(1,-1);
DFS(1,-1,ans);
cout<<ans<<endl;
return 0;
}