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+k2−1]内有多少个与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; }