Educational Codeforces Round 81

E

题目大意

给定一个排列pp,和一个数组aa表示这个排列的每个位置的能量值,要求从中间某个位置把排列分成左右两段,然后把左边的一些数移动到右边,右边的一些数移动到左边,使得左边的所有值小于右边的所有值。某个值移动的代价为该位置的能量值。求满足条件所需要的最小能量值。注意,如果有一边没有数,我们也认为这满足了上述的条件(前提假则整个命题为真嘛)。

题解:
首先可知道的是最后答案肯定是两个集合第一个是空集要么是【1~k】,第二个就是【k+1~n】;

所以我们要枚举k,还要枚举割点应该从哪里割,这时我们发现对于K,我们可以用前缀和算出来表示第一个集合为不同K的所花成本(因为我们左边最终肯定是1 2 3 4...这种);

例如我们k=1,表示最终形成左边集合为1,右边的是2~n,k=3,表示最终形成左边为1 2 3,右边为4~n的两个集合;

那在各个k的条件,接下来就是割点选择使得变换的花费所化成本最少

我们用for遍历割点,假设当前割点i,i q w e ,u o p..

假设i=5,那么【1,4】这里都应该加上5的权重-a【5】,表示k=1~4的这些在变成k+1=5,就应该从右边集合里拿出来到左边集合所增加的花费,所以【5,n】这里的就应该减去a【5】抵消掉之前把他列入右集合的花费,

也就是形成一下俩种画面你割了个5,你选择k=4,也就是左边免费得个5但是得额外补充1 2 3 4花费,或者,右边已选择好5 6 7 ..n的花费预算好,此时把5所准备好的花费去掉。

那为什么不直接从4哪里加上a【5】,1 2 3也要呢,因为你预先花费1 2,因为我们割点是会移动的,后面可以割到 5 3 4这种出来。

所以我们就是线段树维护区间,每个点为k的花费值(所以有l=0,r=n,而不是l=1,r=n,因为可能不需要你从右边拿k个东西,可能一开始就可以分割出完美的两个),取min就完事。

#include
#define ll long long
using namespace std;
int n,p[200005],a[200005],id[200005];
ll minv[800005],val[200005],addv[800005];
void pushup(int rt)
{
    minv[rt]=min(minv[rt<<1],minv[rt<<1|1]);
}
void pushdown(int rt)
{
    if(addv[rt])
    {
        ll t=addv[rt];
        minv[rt<<1]+=t;minv[rt<<1|1]+=t;
        addv[rt<<1]+=t;addv[rt<<1|1]+=t;
        addv[rt]=0;
    }
}
void build(int rt,int l,int r)
{
    int mid=(l+r)>>1;
    if(l==r){minv[rt]=val[l];return;}
    build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
    pushup(rt);
}
void add(int rt,int l,int r,int ql,int qr,ll v)
{
    int mid=(l+r)>>1;
    if(ql<=l&&r<=qr)
    {
        addv[rt]+=v;minv[rt]+=v;
        return;
    }
    pushdown(rt);
    if(ql<=mid)add(rt<<1,l,mid,ql,qr,v);
    if(qr>mid)add(rt<<1|1,mid+1,r,ql,qr,v);
    pushup(rt);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&p[i]),id[p[i]]=i;
    for(int i=1;i<=n;++i)scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)val[i]=val[i-1]+a[id[i]];
    build(1,0,n);
    ll ans=(ll)1e18;
    for(int i=1;i 
 

  D

gcd(a,m)=xgcd(a,m)=x ,有a=k1x,m=k2x
又知道gcd(a+b,m)=x,有a+b=k3
现在问题就变成了,在闭区间内[k1,k1+k21]内有多少个与k2 互质
拆成前缀问题 ,对n 分解质因数后对因子容斥原理即可

#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair PII;
#define X first
#define Y second
inline LL read()
{
    LL x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int T;
LL a,m,x,k1,k2;
LL gcd(LL a,LL b){return b==0 ? a : gcd(b,a%b);}
#include 
vector pme;
LL count_prime(LL x,LL n){
    pme.clear();
    LL i,j;
    for(i=2;i*i<=n;i++)
        if(n%i==0){
            pme.push_back(i);
            while(n%i==0)n/=i;
        }
    if(n>1)pme.push_back(n);
    LL sum=0,value,cnt;
    for(i=1;i<(1< 
 

  C

给你·两个字符串

s,t,要求你每次利用s字符串的子序列构造t,花费最少次数。

 

pre[i]表示字母ii 的最早出现位置,suf[i][j] 表示表示在第ii 位字符后,字母jj 最早出现位置,这个可以用O(26n)O(26n) 预处理出来
然后就直接在上面左右反复横跳就可以了

#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair PII;
#define X first
#define Y second
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxl=100010;
int T;
char s[maxl],t[maxl];
int pre[30],suf[maxl][30];//pre(i)表示字符i最早出现位置 ,suf(i,j)表示在i字符后,字符j最早出现位置 
int main()
{
    T=read();
    while(T--)
    {
        mem(pre,42);mem(suf,42);
        scanf("%s%s",s,t);
        int l1=strlen(s),l2=strlen(t),cnt=0,ok=0;
        for(int i=0;i=0;i--)
            for(int j=0;j<26;j++)suf[i][j]=min(suf[i][j],suf[i+1][j]);
        for(int i=0;i=l1){ok=1;break;}
            while(suf[pos][t[i]-'a'] 
 

  B

属实破题卡超久,首先给你一个由01构成的字符串,你将可以用这个字符串不断复制粘贴出一个无限长的字符串,问你由有多少个前缀可以满足0的个数减掉1的个数等于给定的x

#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair PII;
#define X first
#define Y second
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=100010;
int T,n,x,p0[maxn],p1[maxn],pre[maxn],MAX;
char s[maxn];
int main()
{
    T=read();
    while(T--)
    {
        mem(p0,0);mem(p1,0);mem(pre,0);
        n=read();x=read();
        int cnt=0;
        scanf("%s",s+1);
        for(int i=1;i<=n;i++)p0[i]=p0[i-1]+(s[i]=='0'),p1[i]=p1[i-1]+(s[i]=='1'),pre[i]=p0[i]-p1[i];
        if(x==0)cnt=1;
        if(pre[n])
        {
            for(int i=1;i<=n;i++)if((x-pre[i])%pre[n]==0 && (x-pre[i])/pre[n]>=0)cnt++;
        }
        else
        {
            for(int i=0;i<=n;i++)if(x==pre[i])cnt++;
        }
        if(!pre[n] && cnt)printf("-1\n");
        else printf("%d\n",cnt);
    }
    return 0;
}

  

 

你可能感兴趣的:(Educational Codeforces Round 81)