$$ \newcommand{\seq}[2]{{#1}_{1},{#1}_{2},\cdots,{#1}_{#2}} \newcommand{\num}[1]{1,2,\cdots,#1} \newcommand{\stra}[2]{\begin{bmatrix}#1 \\ #2\end{bmatrix}} \newcommand{\strb}[2]{\begin{Bmatrix}#1 \\ #2\end{Bmatrix}} \newcommand{\dw}[1]{\underline{#1}} \newcommand{\up}[1]{\overline{#1}} $$

Codeforces 314 and 315

315 C

题意

\(n\) 个元素,有 \(a,d\) 两个属性

\(d_i\) 的计算公式为

现在找到第一个满足 \(d_i\le k\) 的元素,将其删除,重新计算其余元素的 \(d_i\)
重复进行此操作,直到没有可以删除的元素为止
依次输出被删除元素的编号
(\(1 ≤ n ≤ 2·10^5\))

Examples

Input
5 0
5 3 4 1 2
Output
2
3
4
Input
10 -10
5 5 1 7 5 1 2 4 9 2
Output
2
4
5
7
8
9

推了很久,终于推出来了
将公式变形,每次删除掉下标为 \(k\) 的元素之后,其余元素的 \(d_i\) 的变化为:

\[d_i+=a_i*(i-1)(i<k) \]

\[d_i+=-\sum_{j=k+1}{i-1}a_j+(n-i)*a_i-a_k*(k-1) \]

用一个sum变量维护 \(\sum_{j=k+1}{i-1}a_j\) 部分,cnt变量维护 \((n-i)*a_i\) 部分,add变量维护 \(a_k*(k-1)\) 部分
\(O(n)\) 扫即可

Code

#include<bits/stdc++.h>
#define maxn 200003
using namespace std;
long long n,cnt,k,a[maxn],d[maxn],add,sum;
int main(){
    scanf("%lld%lld",&n,&k);
    sum=0;
    for(int i=1;i<=n;i++){
        scanf("%lld",a+i);
        d[i]=sum-(i-1)*(n-i)*a[i];
        sum+=(i-1)*a[i];
    }
    sum=0;
    for(int i=1;i<=n;i++){
        if(d[i]-sum+cnt*(n-i)*a[i]-add<k){
            printf("%d\n",i);
            add+=a[i]*(i-1);
            cnt++;
        }
        else{
            sum+=a[i]*cnt;
        }
    }
    return 0;
}

315 D

题意

\([a,b]\) 表示周期为 \(b\) ,每个周期中的字符串为 \(a\) 的一个字符串
现在给你两个串 \(a,b\),两个正整数 \(c,d\)
问满足 \([[b,d],p]\)\([a,c]\) 的子序列的最大的 \(p\) 是多少
( \(1\le |a|,|b|\le 100,1\le c,d\le 10^7\) )

Examples

Input
10 3
abab
bab
Output
3

令字符串下标从一开始
维护一个 \(f\) 数组, \(f[i]\) 表示 \(b[i..|b|]\)\(a\) 中出现了几次
再来一个 \(nxt\) 数组(有一点点类似kmp), \(nxt[i]\) 表示 \(b\) 匹配 \(a\) 匹配完一遍以后指 \(b\) 的指针指在哪个位置
最后,for(int i=1;i<=c;i++)ans+=f[pos],pos=nxt[pos];
然后手膜一遍,发现效率异常高,达到了 \(O(n)\)

Code

#include<bits/stdc++.h>
#define maxn 103
using namespace std;
char s[maxn],t[maxn];
int a,b,n,m,f[maxn],nxt[maxn],ans,pos;
int main(){
    scanf("%d%d%s%s",&a,&b,s+1,t+1);
    n=strlen(s+1),m=strlen(t+1);
    for(int i=1;i<=m;i++){
        pos=i;
        for(int j=1;j<=n;j++){
            if(t[pos]==s[j])pos++;
            if(pos==m+1)f[i]++,pos=1;
        }
        nxt[i]=pos;
    }
    pos=1;
    for(int i=1;i<=a;i++){
        ans+=f[pos];
        pos=nxt[pos];
    }
    printf("%d\n",ans/b);
    return 0;
}

315 E

题意

给你一个序列,找出该序列的所有最长非减子序列(不能有重复元素),求所有这些子序列的价值的和。
一个子序列的价值定义为该序列中所有元素的乘积
答案膜 \(10^9+7\)
( \(1\le n\le 10^5,1\le a_i\le 10^6\) )

Examples

Input
1
42
Output
42
Input
3
1 2 2
Output
13
Input
5
1 2 3 4 5
Output
719

\(dp[i]\) 表示枚举到第 \(i\) 个元素的答案
凑一凑,转移方程就能出来: \(dp[i]=\sum_{a[j]\le a[i]\;and\;j<i}dp[j]*a[j]+a[j]\)
等式右边的左侧的前缀和可以用树状数组维护
但是,不能有重复元素!!!
注意到 \(a_i\le 10^6\)
所以我们另设一个数组 \(last\)\(last[a[i]]\) 表示上一个与 \(a[i]\) 相等的元素的 \(dp\)
\(dp[i]\) 减去 \(last[a[i]]\) 即可
但是,更新 \(last[a[i]]\) 时切记不可以直接 \(last[a[i]]=dp[i]\) !!!

Code

#include<bits/stdc++.h>
#define maxn 100003
#define mod 1000000007
using namespace std;
long long dp[maxn],last[1000003],ans,a[maxn],mx,t[1000003];
int n;
void add(int pos,long long k){
    while(pos<=mx){
        t[pos]=(t[pos]+k)%mod;
        pos+=pos&-pos;
    }
}
long long query(int pos){
    long long ret=0;
    while(pos){
        ret=(ret+t[pos])%mod;
        pos-=pos&-pos;
    }
    return ret;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",a+i),mx=max(mx,a[i]);
    for(int i=1;i<=n;i++){
        dp[i]=(mod+(query(a[i])*a[i]%mod+a[i])%mod-last[a[i]])%mod;
        last[a[i]]=(query(a[i])*a[i]%mod+a[i])%mod;
        ans=(ans+dp[i])%mod;
        add(a[i],dp[i]);
    }
    printf("%lld\n",ans);
    return 0;
}

314 D

题意

平面上有 \(n\) 个点,现在有两条直线,互相垂直,与 \(x\) 轴呈 \(45°\) 角,定义 \(dis(i)\) 为点 \(i\) 到两条直线的曼哈顿距离( \(|x_1-x_2|+|y_1-y_2|\) )中较小的一个
移动两条直线使得 \(\max\{dis(i)\}\) 最小
( \(1\le n\le 10^5\) )

Examples

Input
4
0 0
2 0
0 2
2 2
Output
0.000000000000000
Input
4
1 0
0 1
2 1
1 2
Output
1.000000000000000

发现点 \(i\) 到某条直线的曼哈顿距离必定是点 \(i\) 到这条直线的距离的 \(\sqrt{2}\) 倍。
所以我们将整个坐标轴旋转 \(45°\) ,这样,原来坐标为 \((x,y)\) 的点变成了 \((x-y,x+y)\) ,而且坐标还是整数(long long即可解决)。
问题转化为求两条分别与x轴、y轴平行的直线
将点按x坐标排序
然后我们二分 \(\max\{dis(i)\}\)
设二分出的值为mid
用两个指针维护距离至多为2mid的两条平行于y轴的直线
两条直线中间的点一定是合法的
问题在于两条直线之外的点
其实,只要两条直线之外的点y坐标最大值减最小值是小于等于2
mid的,我们就构造出了一种可行解,return true
前缀、后缀的最大值、最小值可以先预处理出来

Code

#include<bits/stdc++.h>
#define maxn 100003
#define INF 100000000000000000ll
using namespace std;
struct point{
    long long x,y;
    point(){}
    point(long long _x,long long _y):x(_x),y(_y){}
    bool operator <(const point& p)const{return x==p.x?y<p.y:x<p.x;}
};

point a[maxn];
int n;
long long mx1[maxn],mi1[maxn],mx2[maxn],mi2[maxn];
bool check(long long M){
    for(int i=1,j=1;i<=n;i++,j=max(i,j)){
        while(j<=n&&a[j].x-a[i].x<=M)j++;j--;
// printf("i:%d j:%d\n",i,j);
        if(max(mx2[j+1],mx1[i-1])-min(mi2[j+1],mi1[i-1])<=M)return 1;
    }
    return 0;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        long long x,y;
        scanf("%lld%lld",&x,&y);
        a[i]=point(x-y,x+y);
    }
    sort(a+1,a+n+1);
    mx1[0]=mx2[n+1]=-INF,mi1[0]=mi2[n+1]=INF;
    for(int i=1;i<=n;i++){
        mx1[i]=max(mx1[i-1],a[i].y);
        mi1[i]=min(mi1[i-1],a[i].y);
    }
    for(int i=n;i>=1;i--){
        mx2[i]=max(mx2[i+1],a[i].y);
        mi2[i]=min(mi2[i+1],a[i].y);
    }
    long long l=0,r=4000000000ll,mid,ans=r;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%.12lf\n",ans*0.5);
    return 0;
}
posted @ 2019-02-16 15:38  chc_1234567890  阅读(210)  评论(0编辑  收藏  举报