Codeforces Round #622 (Div. 2)
题目链接:https://codeforces.com/contest/1313
A. Fast Food Restaurant
算贪心吧,先一个一个选,再两个两个选,最后三个三个选,选两个的时候注意挑选最多的两个。
B. Different Rules
推公式吧,感觉就和田忌赛马差不多,但是我wa了几次,就是当两次排名都是倒数第一时,我会算出负数。
代码: 一时兴起用了java
import java.util.*;
import java.math.*;
import java.io.*;
public class a1{
public static void main(String[] args){
Scanner cin=new Scanner(System.in);
Long q=cin.nextLong();
for(int i=1;i<=q;i++){
Long n,a,b,ans1,ans2;
n=cin.nextLong();
a=cin.nextLong();
b=cin.nextLong();
ans1=n-Math.max(0,Math.min(n*2-b-a-1,n-1));
ans2=Math.min(a+b-1,n);
System.out.println(ans1+" "+ans2);
}
}
}
C1 C2 Skyscrapers
题解
C1可以直接枚举极大值点然后再扫一遍数组,(O(n^2)).
C2就是在枚举最大值时优化求值过程。
我们创建一个数组(f[i])表示(1..i变成单增序列后的权值和)。那么从(f[i-1]到f[i])肯定是有联系的。我们只要找到左边第一个比(i位置权值小的位置,将他记作x),则:
- (f[i]=f[x]+(i-x)*a[i],(a[i]是i点的权值))。
那么问题就变成了怎么求左边第一个比(a[i]小的数的位置),这个可以用单调队列。当然也可以用一种神奇的方法,那就是先判断(a[i]和a[i-1]的大小关系):
-
(a[i-1]le a[i]) : (L[i]=i-1); ((L[i]为i左边第一个大于等于a[i]的数的位置))
-
(a[i]>a[i-1]) : (那么再判断a[L[i-1]]和a[i]的关系)
这样貌似时间复杂度得不到保证,但是实际上速度很快,应该是(O(n))。我们可以分析一下什么时候回往前跳。就是当(a[i-1]>a[i]),但是之后就再也不会跳到(a[i-1])了,因为(L[i]<i-1),也就是每个点跳一次就不会再继续碰到它了。所以当最多跳(n)次。
当然感觉这种方法比较非主流,好好的单调队列不用,用这种奇葩算法。
代码一 非主流算法 (O(n))
import java.util.*;
import java.math.*;
import java.io.*;
public class a1{
static class G{
final static int N=1000050;
int n;
int[] a=new int[N];
int[] l=new int[N];
Long[] f=new Long[N];
public int findL(int i,int p){
if (a[i]<p)return i;
else return findL(l[i],p);
}
public Long findVal(int i, int p){
if (a[i]<p) return f[i];
else return findVal(l[i],p)+(long)(i-l[i])*p;
}
public void work(){
a[0]=0;
l[1]=0;
f[0]=0L;
for(int i=1;i<=n;i++)
l[i]=findL(i-1,a[i]);
for(int i=1;i<=n;i++)
f[i]=findVal(i-1,a[i])+Long.valueOf(a[i]);
}
public void handle(int k){
for(int i=k-1;i>=1;i--)
a[i]=Math.min(a[i],a[i+1]);
for(int i=k+1;i<=n;i++)
a[i]=Math.min(a[i],a[i-1]);
}
}
public static void main(String[] args){
Scanner cin=new Scanner(System.in);
G arr1=new G();
G arr2=new G();
int n;
n=arr2.n=arr1.n=cin.nextInt();
for(int i=1;i<=n;i++)
arr1.a[i]=cin.nextInt();
arr1.work();
for(int i=1;i<=n;i++)
arr2.a[i]=arr1.a[n+1-i];
arr2.work();
Long ans=0L;
int tp=0;
for(int i=1;i<=n;i++){
if (arr1.f[i]+arr2.f[n-i+1]-Long.valueOf(arr1.a[i])>ans){
ans=arr1.f[i]+arr2.f[n-i+1]-Long.valueOf(arr1.a[i]);
tp=i;
}
}
arr1.handle(tp);
for(int i=1;i<=n;i++)
System.out.print(arr1.a[i]+" ");
}
}
代码二 单调队列 (O(n))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
using vl=vector<ll>;
using pll=pair<ll,ll>;
int n;
void solve(vl &a, vl &ans){
deque<pll>Q;
for(int i=1;i<=n;i++){
while(!Q.empty() && a[i]<=Q.back().second)Q.pop_back();
int pre=Q.empty()?0:Q.back().first;
ans[i]=ans[pre]+a[i]*(i-pre);
Q.push_back({i,a[i]});
}
}
void print(vl &a,ll k, vl &ans){
ans[k]=a[k];
for(int i=k-1;i>=1;i--){
ans[i]=min(a[i],ans[i+1]);
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("aa.in","r",stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
vl init(n+5,0);
vl a,a_rev,ansl,ansr;
pll max={0,0};
a=a_rev=ansl=ansr=init;
for(int i=1;i<=n;i++) cin>>a[i],a_rev[n+1-i]=a[i];
solve(a,ansl);
solve(a_rev,ansr);
for(int i=1;i<=n;i++)
if (ansl[i]+ansr[n+1-i]-a[i]>max.second){
max={i,ansl[i]+ansr[n+1-i]-a[i]};
}
print(a,max.first,ansl);
print(a_rev,n-max.first+1,ansr);
for(int i=1;i<=max.first;i++)cout<<ansl[i]<<" ";
for(int i=n-max.first;i>=1;i--)cout<<ansr[i]<<"
"[i==1];
}
D. Happy New Year
题解:
(kle8),虽然我也知道可以对于每个点暴力枚举覆盖情况,也就(2^k)种状态。
但是枚举完之后我不知到怎么由前面的状态转移,主要是有很多区间交在一起,我不会处理。上网看了题解,发现一个神奇的东西,扫描线。也就是把所有的端点变成两个独立点,再标记它是左端点还是右端点。具体步骤:
- 先将端点数组排序
- 再扫描这个数组,
- 碰到左端点,就给他分配一个编号(1..k中的一个),并进行处理
- 碰到右端点,就空出来原来的编号,并处理一下
感觉这么说完,还是很抽象。我们可以建一个数组(dp[i][j])表示,第(i)个端点所表示的距离,状态为(0le j<2^k)的最大覆盖情况。这个(dp)讲起来有点麻烦,这里还是讲一下思路吧。因为如果前面和当前端点没有交集的区间我们只需要求一个最大值即可,而相交的部分,就可以通过设置状态来解决,这样就可以完成转移。具体可以看代码。
代码:
#include<bits/stdc++.h>
using namespace std;
using vi=vector<int>;
using pii=pair<int,int>;
using vpii=vector<pii>;
const int INF=0x7f7f7f7f;
bool cmp(pii a, pii b){return a.first==b.first?a.second>b.second:a.first<b.first;};
int n,m,k;
int main(){
#ifndef ONLINE_JUDGE
freopen("aa.in","r",stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>k;
vpii p;
vi bit,dp(1<<k,-INF),newdp,init(1<<k,0);
vi num(n+50,0);
set<int> S;
bit=newdp=init;
for(int i=0;i<k;i++)S.insert(i);
for(int i=1;i<(1<<k);i++)bit[i]=bit[i>>1]^(i&1);
for(int i=1;i<=n;i++){
int l,r;
cin>>l>>r;
p.push_back({l,i});
p.push_back({r,-i});
}
sort(p.begin(),p.end(),cmp);
int pre=p[0].first-1; dp[0]=0;
for(int i=0;i<p.size();i++){
int x=p[i].first, id=p[i].second;
for(int j=1;j<(1<<k);j++) dp[j]+=(x-pre)*bit[j];
if (id>0){
num[id]=*S.begin(); S.erase(S.begin());
for(int j=0;j<(1<<k);j++){
if ((j>>num[id])&1) newdp[j]=dp[j^(1<<num[id])]+2*bit[j]-1;
else newdp[j]=dp[j];
}
}else{
id=-id;
S.insert(num[id]);
int val=x-pre;
for(int j=0;j<(1<<k);j++){
if ((j>>num[id])&1){
newdp[j^(1<<num[id])]=max(dp[j^(1<<num[id])],dp[j]);
newdp[j]=-INF;
}else{
newdp[j]=dp[j];
}
}
}
pre=x;
dp=newdp;
}
cout<<dp[0];
}