C:
给出一个X,Y
求出一个序列
A
,满足
模拟,序列第一位为X,每一位为之前一位的两倍, O(logY) 。
D:
给出一个01串,可以将01串上任意连续的长度不小于k的一个子序列翻转,求k的最大值。
很容易发现一点,假设我们将翻转的区间的一端固定在整个序列的端点,那么我们可以通过翻转前k个点,再翻转前k+1个点来满足单点翻转(即k+1号点的翻转),这样一来,我们可以确定
除了中间2K-N个点以外,所有的点都可以不影响其他点的情况下进行翻转。
相对应的,再考虑中间这2K-N个点,发现每次翻转必定会包含这个连续的区间,也就是说这个区间内的点,被翻转情况是一致的。现在答案就很显然了,从中间开始,寻找尽量多的连续的相同的区间,这个区间的长度越长,k就越大。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 100010
using namespace std;
char a[MAXN];
int main(){
SF("%s",a);
int len=strlen(a);
if(len%2==0){
int mid=len/2-1;
int y=0;
if(a[mid]==a[mid+1]){
y=1;
while(mid-y>=0&&a[mid-y]==a[mid]&&a[mid-y]==a[mid+1+y])
y++;
}
y--;
PF("%d",mid+y+2);
}
else{
int mid=len/2;
int y=1;
while(mid-y>=0&&a[mid-y]==a[mid]&&a[mid+y]==a[mid])
y++;
y--;
PF("%d",mid+y+1);
}
}
E:
给出一个仅由小写字母组成的字符串,可以交换相邻两个字符,求最终能将整个字符串转化为回文串的最小操作次数,如不能做到,输出-1
经典的贪心题
设这个字符串为
c1c2....cn−1cn
很容易想到,将与
c1
相同的字符移动到最右端是最优的,
证明很容易:
无论如何,必须有一个点在1号位置与n号位置的点相同,那么如果是除了
c1
以外的其他点,那么必然最终会将这两个点移动到两端,这个操作显然是多余的,
如上图红色箭头所指的位置必然比黑色箭头的位置要优一些。
如果是单个节点(即没有与之相同的另一个未匹配字符),这个节点必然会移动到中间。这里为了统计操作次数,需要用到树状数组来存储前缀和。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 200010
using namespace std;
char a[MAXN];
vector<int> topx[50];
bool del[MAXN];
int las[50],n,sum;
long long ans,tree[MAXN];
void change(int x,int adx){
while(x<=n){
tree[x]+=adx;
x+=x&(x^(x-1));
}
}
long long query(int x){
long long res=0;
while(x){
res+=tree[x];
x-=x&(x^(x-1));
}
return res;
}
int main(){
SF("%s",a);
n=strlen(a);
for(int i=0;i<n;i++){
topx[a[i]-'a'].push_back(i);
}
for(int i=0;i<=30;i++)
las[i]=topx[i].size()-1;
for(int i=0;i<=30;i++)
if(topx[i].size()%2==1)
sum++;
if(sum>1){
PF("-1");
return 0;
}
for(int i=1;i<=n;i++)
change(i,1);
for(int i=0;i<n;i++)
if(del[i]==0){
int x=a[i]-'a';
int y=topx[x][las[x]];
int u=i+1;
int v=y+1;
del[i]=1;
del[y]=1;
if(u==v){
ans+=(query(n)-query(u))/2;
}
else{
ans+=query(n)-query(v);
}
change(u,-1);
change(v,-1);
las[x]--;
}
PF("%lld",ans);
}
F:
PS:这道题让我深切体会到了JP人民的智(wu)慧(liao)与善(e)良(xin),从浅显易懂(disgusting)的题目描述到实现细节,都非常的优(wei)美(suo)。
题意:
给出一颗树,已知这棵树是按照一种特定的方式生成的:
首先有A条长度最长为B的链,
每次选中两个不连通的点,
将这两个点合并成一个点,直到形成一颗树(也就是给出的树)
求在A最小的情况下,A的值与B的最小值。
我们当然不能从构造的角度来看这个问题,
不难发现,每条最初的链都会形成树上的一条树链,
也就是说我们要将这棵树分成尽量少的树链,并在此基础上使得最长的链最短。
对于A的最小值应该是很容易想到的,其实就是奇度点的个数的一半。
很容易解释,为了使得树链尽量少,我们不可能将同一个点作为2条及以上的树链的端点(那样就可以合并这两条树链,进而减少树链的数量)。
因此,偶度点是不可能作为树链的端点的,也很容易想到,奇度点必然会成为一条树链的端点,因此,总的树链的个数就是奇度点的个数的一半。
再来解决第二问:
很容易想到树DP,设dp[i]表示到达i点时最短的路径。。。因为这只能判断最长长度小于等于某个特定值。所以只要加一个二分答案就可以了。
然后是一堆实现细节。。。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<set>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 100010
using namespace std;
int dp[MAXN];
int l,r,n,sum,ans;
multiset<int> s;
vector<int> a[MAXN];
bool dfs(int x,int fa,int maxl){
for(int i=0;i<a[x].size();i++)
if(a[x][i]!=fa)
if(dfs(a[x][i],x,maxl)==0)
return 0;
s.clear();
for(int i=0;i<a[x].size();i++)
if(a[x][i]!=fa){
if(dp[a[x][i]]+1>maxl){
return 0;
}
s.insert(dp[a[x][i]]+1);
}
int flag=0;
if(s.size()%2==1)
flag=1;
if(x==1)
flag--;
while(s.size()>1){
int t=*s.rbegin();
s.erase(s.find(t));
multiset<int>::iterator it=s.lower_bound(maxl-t+1);
if(it==s.begin()){
if(flag==-1){
return 0;
}
if(flag==1){
dp[x]=t;
flag=-1;
}
else{
if(x!=1)
flag=1;
else
flag=-1;
}
}
else{
it--;
s.erase(it);
}
}
if(s.size()==1){
dp[x]=*s.begin();
if(dp[x]>maxl){
return 0;
}
s.erase(dp[x]);
}
return 1;
}
bool check(int maxl){
memset(dp,0,sizeof dp);
s.clear();
if(dfs(1,0,maxl))
return 1;
return 0;
}
int u,v;
int main(){
//freopen("data.in","r",stdin);
//freopen("data.out","w",stdout);
SF("%d",&n);
for(int i=1;i<n;i++){
SF("%d%d",&u,&v);
a[u].push_back(v);
a[v].push_back(u);
}
for(int i=1;i<=n;i++)
if(a[i].size()%2==1)
sum++;
sum/=2;
PF("%d ",sum);
//PF("[%d]",check(6));
l=1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)){
ans=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
PF("%d",ans);
}
比赛总结:
嗯。。与上一次做Atcoder相比,这次做得还不错,过了前三题(第四题题意太长不想看),但简单的代码实现了很久!第三题的树状数组居然写了我半个小时!