算是第一次打\(CF\)吧(而且还是\(Virtual\,participation\)),觉得题目思维含量挺高,只是两小时做\(7\)道题未免有些太困难了吧。其实主要是自己比较菜,思维水平不够, 有些知识与算法掌握得还不够多、理解不够深入。总而言之,仍要多加训练。话不多说,接下来谈题目。
Problem A. Codehorses T-shirts
题目大意:给你两张表,每张表上有\(n\)件衣服的尺码,若将尺码中的一个字母改成另一个字母(改完后仍要合法)需要花费\(1s\),那么请求出将第一张表上的尺码全部改成第二张表上的尺码最短要多长时间(不得增加或删减字母,题目保证有解)。合法的尺码有:\(M\)、\(S\)、\(L\)、\(XS\)、\(XL\)、\(XXS\)、\(XXL\)、\(XXXS\)、\(XXXL\)。
思路:可以发现,只有两个长度相同的尺码可以相互更改,并且两个长度相同的尺码最多只需要花费\(1s\)就可以从一种改成另一种尺码。所以?我们只要统计出两张表中相同的尺码数\(w\),那么显然\(n-w\)就是答案,因为相同的尺码不必再改,不相同的也只需要花费\(1s\)。所以有如下代码:
#include<cstdio>
#include<iostream>
#include<map>
using namespace std;
const int N=1e2+10;
map<string,int>p;
int n,num,check[N],ans;
string s;
int main(){
scanf("%d",&n);
for(register int i=1;i<=n;i++){
cin>>s;if(!p[s]){p[s]=++num;}
check[p[s]]++;}
for(register int i=1;i<=n;i++){
cin>>s;
if(check[p[s]])check[p[s]]--;
else ans++;}
printf("%d\n",ans);
return 0;
}
一开始把check[p[s]]打成check[num]挂了好长时间,下次注意
Problem B. Light It Up
题目大意:一个长度为\(m\)的序列,上面有\(n\)个节点,起初灯处于开着的状态,此后从\(0\)开始每经过序列上的一个节点就会变为相反的状态,你可以在序列里安装至多\(1\)个节点,使得灯亮的时间最长。计算方法是:若灯在两个节点\(x\)与\(y\)之间是亮着的,那么它发亮的时间就是\(|y-x|\)。
思路:首先,若不安装多余的节点,那么\(ans\)的值应当等于\(\sum_{i=0}^{n/2}a[2*i+1]-a[2*i]\),其中\(a[i]\)表示第\(i\)个节点与\(0\)点的距离。接下来考虑若是安装了一个节点该怎么办。直接枚举\(i=[1...m-1]\)安装节点?对于\(m\le10^9\)的数据显然不现实,观察后发现\(n\)的数据范围较小,可以考虑在每个节点\(k\)左右考虑枚举\(i\),那么这样到底可不可以呢?思索一番发现,若\(k\)左侧原本是开着的,那么我们枚举\(i=(a[k-1]+1...a[k]-1)\)时显然不会有选择\(i=a[k]-1\)时优,因为前一种方法中明明可以将\(i\)向右移动使得其发亮的时间更长,而后一种方法则将这一段区间内不发亮的时间压到了最短,所以最优。如此,右侧原本是开着的同理。这样,我们先预处理若起初是关着的,到每一个节点的发亮时间,然后对于每一个节点,判断放左还是右(算出的结果其实是一样的),然后把之前预处理好的数组嫁接进去就可以了,复杂度\(O(n)\)。具体看代码:(部分冗余)
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,m,a[N],ans,ans1[N],ans2[N];
int main(){
scanf("%d%d",&n,&m);
for(register int i=2;i<=n+1;i++)scanf("%d",&a[i]);
a[1]=0;a[n+2]=m;n+=2;
for(register int i=3;i<=n;i+=2)ans1[i]=ans1[i-2]+a[i]-a[i-1],ans1[i+1]=ans1[i];
for(register int i=2;i<=n;i+=2)ans2[i]=ans2[i-2]+a[i]-a[i-1],ans2[i+1]=ans2[i];
ans=ans2[n];
for(register int i=1;i<=n;i++){
if(a[i-1]+1!=a[i]&&i!=1){
if(ans2[i]!=ans2[i-1])ans=max(ans,ans2[i]-1+ans1[n]-ans1[i-1]);
else ans=max(ans,ans2[i]+1+ans1[n]-ans1[i]);}
if(a[i+1]-1!=a[i]&&i!=n){
if(ans2[i]!=ans2[i-1])ans=max(ans,ans2[i]-1+ans1[n]-ans1[i-1]);
else ans=max(ans,ans2[i]+1+ans1[n]-ans1[i]);}
}
printf("%d\n",ans);
return 0;
}
Problem C. Covered Points Count
题目大意:一条线段上有\(n\)个区间,每个区间的覆盖范围为\(l-r\),要求输出被覆盖了\(k=[1...n]\)次的点的个数
思路:一般的差分就是开个数组,在位置\(l\)上加一,在位置\(r+1\)上减一,然后处理完\(n\)个区间后从头扫到尾统计一下就可以了。但是,这题的\(l\)与\(r\)的范围竟然已经达到了\(10^{18}\)!所以一般的方法是不行的。一开始考虑离散化,但由于压缩后内部点的个数处理不当导致程序挂掉了,于是放弃。回顾一般性方法,我们在从头扫到尾的过程中,真正起到作用的只是不为\(0\)的位置,而我们程序大部分时间都用在值为\(0\)的位置上了。类似于第二题,我们不必从头扫到尾,而是把每段区间拆成两二元组\((l,1)\)与\((r+1,-1)\),并在最后按照位置顺序对所有二元组进行排序,再将这些二元组从头扫到尾即可,复杂度\(O(nlogn)\)。具体细节还是得看代码,主要是只有一个位置上所有的二元组都处理完后才可以累加答案。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2e5+20;
int n,sum;
ll c,l,r,ans[N*10];
struct question{
ll x,w;
}q[N*2];
bool cmp(question w,question e){return w.x<e.x;}
int main(){
scanf("%d",&n);
for(register int i=1;i<=n;i++){
scanf("%lld%lld",&l,&r);
q[++c].x=l;q[c].w=1;
q[++c].x=r+1;q[c].w=-1;}
sort(q+1,q+c+1,cmp);
for(register int i=1;i<=c;i++){
sum+=q[i].w;
if(q[i].x!=q[i+1].x)ans[sum]+=q[i+1].x-q[i].x;
}
for(register int i=1;i<=n;i++)printf("%lld%c",ans[i],i==n? '\n':' ');
return 0;
}
Problem D. Yet Another Problem On a Subsequence
题目大意:(此题较有趣稍后再补题解)
Problem E. We Need More Bosses
题目大意:对于一个\(n\)个点\(m\)条边的无向图,请找出两个点\(x\),\(y\),使得\(x-y\)这条路径上必须经过的边最多,并输出最多的边数
思路:必须经过的边?桥?首先我们知道若有环的话,环上任意一条边都不可能是必须经过的边,所以缩点。那么剩下的边都不属于任意一个环,也就是说,剩下的边若在原图中是\(x-y\)路径上的一条边,那么其一定是必须要经过的边,第一个条件满足。然后要求经过最多的边的个数。观察缩过点的图,无向,无环,那么它一定是一棵树。那么问题就转化成了给你一棵树,求出树上的最远点对并输出其距离,这不就是树的直径吗,简单\(dp\)一下就行了,没什么好说的,代码如下:(不喜欢大模板题)
#include<cstdio>
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
const int N=3e5+10;
int n,m,A,B,head[N],cnt,w,cl,pre[N],las[N],scc[N],f[N],dp[N],ans;
bool vis[N];
struct edge{
int to,nxt;
}ed[N*2];
stack<int>S;
vector<int>G[N];
vector<int>SCC[N];
inline void addedge(int x,int y){
ed[++cnt].to=y;ed[cnt].nxt=head[x];
head[x]=cnt;
ed[++cnt].to=x;ed[cnt].nxt=head[y];
head[y]=cnt;
}
void Tarjan(int u,int fa){
pre[u]=las[u]=++cl;S.push(u);
for(register int i=head[u];i;i=ed[i].nxt){
int v=ed[i].to;if(v==fa)continue;
if(!pre[v]){Tarjan(v,u);las[u]=min(las[u],las[v]);}
else if(!scc[v])las[u]=min(las[u],pre[v]);
}
if(pre[u]==las[u]){
w++;
while(1){
int x=S.top();S.pop();
scc[x]=w;SCC[w].push_back(x);
if(x==u)break;}
}
}
void DFS(int u,int fa){
vis[u]=true;int k;
for(register int i=0;i<G[u].size();i++){
int v=G[u][i];if(v==fa||vis[v])continue;
DFS(v,u);if(f[u]<f[v]+1)k=v,f[u]=f[v]+1;}
dp[u]=f[u];
for(register int i=0;i<G[u].size();i++){
int v=G[u][i];if(v==fa||k==v)continue;
dp[u]=max(dp[u],f[u]+f[v]+1);}
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;i++){
scanf("%d%d",&A,&B);addedge(A,B);}
Tarjan(1,0);
for(register int i=1;i<=w;i++)
for(register int j=0;j<SCC[i].size();j++)
for(register int k=head[SCC[i][j]];k;k=ed[k].nxt)
if(scc[ed[k].to]!=i){
G[i].push_back(scc[ed[k].to]);
G[scc[ed[k].to]].push_back(i);}
DFS(1,0);
for(register int i=1;i<=w;i++)ans=max(ans,dp[i]);
printf("%d\n",ans);return 0;
}
Problem F. One Occurrence\((solving)\)
Problem G. Two-Paths\((solving)\)
对不起我还要去补数学和DP优化的博客,暂时搁置。。。
题解预备:POJ3696、NOI2009诗人小G及博弈论相关