[SCOI 第二试 序列操作]线段树

【题目】

Description

lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作:
0 a b 把[a, b]区间内的所有数全变成0
1 a b 把[a, b]区间内的所有数全变成1
2 a b 把[a,b]区间内的所有数全部取反,也就是说把所有的0变成1,把所有的1变成0
3 a b 询问[a, b]区间内总共有多少个1
4 a b 询问[a, b]区间内最多有多少个连续的1
对于每一种询问操作,lxhgww都需要给出回答,聪明的程序员们,你们能帮助他吗?

Input

输入数据第一行包括2个数,n和m,分别表示序列的长度和操作数目
第二行包括n个数,表示序列的初始状态
接下来m行,每行3个数,op, a, b,(0<=op<=4,0<=a<=b

Output

对于每一个询问操作,输出一行,包括1个数,表示其对应的答案

Sample Input

10 10
0 0 0 1 1 0 1 0 1 1
1 0 2
3 0 5
2 2 2
4 0 4
0 3 6
2 3 7
4 2 8
1 0 5
0 5 6
3 3 9

Sample Output

5
2
6
5

Hint

对于30%的数据,1<=n, m<=1000
对于100%的数据,1<=n, m<=100000 【算法分析】
就是线段树。但是注意标记下传的函数要写好,下传覆盖为0或覆盖为1的标记的时候,需要把儿子原本的标记都删掉(因为之前的标记在这个覆盖后都没有用了)。
然后处理标记就先搞覆盖,再弄翻转。因为如果是覆盖操作在后的话,那么所有标记都会消失,而现在标记共存的话,肯定是先覆盖再翻转。注意了这一点以后就很容易AC了。
【CODE】
#include #include #include #include using namespace std;
const int N=100005;
int n,m;
int a[N];
struct Treetype{
struct Node{int l,r,FL0,FL1,FR0,FR1,N0,N1,L0,L1,c0,c1,filp;}tr[N*5];

void pushdown(int pos){
Node &p=tr[pos];
if (p.c0){
p.FL0=p.FR0=p.N0=p.L0=p.r-p.l+1;
p.FL1=p.FR1=p.N1=p.L1=0;
p.c0=0;
if (p.l!=p.r){
tr[pos*2].c0=1;
tr[pos*2].c1=0;
tr[pos*2].filp=0;
tr[pos*2+1].c0=1;
tr[pos*2+1].c1=0;
tr[pos*2+1].filp=0;
}
}
if (p.c1){
p.FL0=p.FR0=p.N0=p.L0=0;
p.FL1=p.FR1=p.N1=p.L1=p.r-p.l+1;
p.c1=0;
if (p.l!=p.r){
tr[pos*2].c1=1;
tr[pos*2].c0=0;
tr[pos*2].filp=0;
tr[pos*2+1].c1=1;
tr[pos*2+1].c0=0;
tr[pos*2+1].filp=0;
}
}
if (p.filp){
swap(p.FL0,p.FL1);
swap(p.FR0,p.FR1);
swap(p.N0,p.N1);
swap(p.L0,p.L1);
if (p.l!=p.r){
tr[pos*2].filp^=1;
tr[pos*2+1].filp^=1;
}
p.filp=0;
}
}

void update(int pos){
Node &p=tr[pos];
pushdown(pos);
if (p.l!=p.r){
pushdown(pos*2);
pushdown(pos*2+1);
}
if (p.l==p.r) return;
Node &L=tr[pos*2],&R=tr[pos*2+1];
p.N0=L.N0+R.N0;
p.N1=L.N1+R.N1;
p.L0=max(L.L0,R.L0); p.L0=max(p.L0,L.FR0+R.FL0);
p.L1=max(L.L1,R.L1); p.L1=max(p.L1,L.FR1+R.FL1);
p.FL0=L.FL0; if (L.FL0==L.r-L.l+1) p.FL0+=R.FL0;
p.FL1=L.FL1; if (L.FL1==L.r-L.l+1) p.FL1+=R.FL1;
p.FR0=R.FR0; if (R.FR0==R.r-R.l+1) p.FR0+=L.FR0;
p.FR1=R.FR1; if (R.FR1==R.r-R.l+1) p.FR1+=L.FR1;
}

void build(int pos,int l,int r){
Node &p=tr[pos];
p.l=l; p.r=r; p.c0=p.c1=p.filp=0;
if (l==r){
if (a[l]){
p.FL0=p.FR0=p.N0=p.L0=0;
p.FL1=p.FR1=p.N1=p.L1=1;
}
else{
p.FL0=p.FR0=p.N0=p.L0=1;
p.FL1=p.FR1=p.N1=p.L1=0;
}
return;
}
int mid=(l+r)/2;
build(pos*2,l,mid);
build(pos*2+1,mid+1,r);
update(pos);
}

void ins(int pos,int l,int r,int op){
Node &p=tr[pos];
if (r update(pos);
if (l<=p.l && p.r<=r){
switch (op){
case 0:p.c0=1; break;
case 1:p.c1=1; break;
case 2:if (p.filp) for (;;); p.filp^=1; break;
}
update(pos);
return;
}
ins(pos*2,l,r,op);
ins(pos*2+1,l,r,op);
update(pos);
}

void solve3(int pos,int l,int r,int &ans){
Node &p=tr[pos];
if (r update(pos);
if (l<=p.l && p.r<=r){
ans+=p.N1;
return;
}
solve3(pos*2,l,r,ans);
solve3(pos*2+1,l,r,ans);
}

void solve4(int pos,int l,int r,int &rr,int &ans){
Node &p=tr[pos];
if (r update(pos);
if (l<=p.l && p.r<=r){
ans=max(ans,p.L1);
ans=max(ans,rr+p.FL1);
if (p.L1==p.r-p.l+1) rr+=p.L1;
else rr=p.FR1;
ans=max(ans,rr);
return;
}
solve4(pos*2,l,r,rr,ans);
solve4(pos*2+1,l,r,rr,ans);
}
}Tree;

int main(){
freopen("operation.in","r",stdin);
freopen("operation.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
Tree.build(1,1,n);
for (int i=1;i<=m;i++){
int x,y,op;
scanf("%d%d%d",&op,&x,&y);
x++; y++;
if (op<=2) Tree.ins(1,x,y,op);
if (op==3){
int ans=0;
Tree.solve3(1,x,y,ans);
printf("%dn",ans);
}
if (op==4){
int tmp=0,ans=0;
Tree.solve4(1,x,y,tmp,ans);
printf("%dn",ans);
}
}
fclose(stdin);
fclose(stdout);
return 0;
}

[SCOI 第二试 字符串]求组合数、n!质因数分解

【题目】

Description

lxhgww最近接到了一个生成字符串的任务,任务需要他把n个1和m个0组成字符串,但是任务还要求在组成的字符串中,在任意的前k个字符中,1的个数 不能少于0的个数。现在lxhgww想要知道满足要求的字符串共有多少个,聪明的程序员们,你们能帮助他吗?

Input

输入数据是一行,包括2个数字n和m

Output

输出数据是一行,包括1个数字,表示满足要求的字符串数目,这个数可能会很大,只需输出这个数除以20100403的余数

Sample Input

2 2

Sample Output

2

Hint

对于30%的数据,保证1<=m<=n<=1000
对于100%的数据,保证1<=m<=n<=1000000 【算法分析】
容易推得公式是C(n,n+m)-C(m-1,n+m)
然后就是求组合数问题。
然后n!有一个快速质因数分解的方法,按照那个分解,然后将除法变为幂减,最终再将个因子乘起来(要用快速幂)。就可以得解。
【CODE】
#include #include #include #include using namespace std;
const int N=2000000;
const int mod=20100403;
typedef long long lld;
bool ss[N+5];
int n1[N/10],n2[N/10],n3[N/10],pl[N/10];
int n,m,tot;
lld s1,s2;

void init(){
int i,j;
memset(ss,true,sizeof(ss));
for (i=2;i*i<=N;i++)
if (ss[i])
for (j=2;i*j<=N;j++)
ss[i*j]=false;
tot=0;
for (i=2;i<=N;i++)
if (ss[i]) pl[tot++]=i;
}

void Div(int *list,int n){
int i,j,tmp;
for (i=0;i for (i=0;i tmp=n;
while (tmp/pl[i]){
list[i]+=tmp/pl[i];
tmp/=pl[i];
}
}
}

lld pow(lld x,lld cf){
if (cf==0) return 1;
lld res=pow(x,cf/2);
res=(res*res)%mod;
if (cf%2) res*=x;
res%=mod;
return res;
}

int main(){
init();
scanf("%d%d",&n,&m);
Div(n1,m+n);
Div(n2,n);
Div(n3,m);
for (int i=0;i n1[i]-=n2[i];
n1[i]-=n3[i];
}
s1=1;
for (int i=0;i s1*=pow(pl[i],n1[i]);
s1%=mod;
}
Div(n1,m+n);
Div(n2,n+1);
Div(n3,m-1);
for (int i=0;i n1[i]-=n2[i];
n1[i]-=n3[i];
}
s2=1;
for (int i=0;i s2*=pow(pl[i],n1[i]);
s2%=mod;
}
lld ans=s1-s2;
ans=((ans%mod)+mod)%mod;
cout << ans << endl;
}

[SCOI 第一试 股票交易]动态规划、单调队列优化

【题目】

Description

最近lxhgww又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。
通过一段时间的观察,lxhgww预测到了未来T天内某只股票的走势,第i天的股票买入价为每股APi,第i天的股票卖出价为每股BPi(数据保 证对于每个i,都有APi>=BPi),但是每天不能无限制地交易,于是股票交易所规定第i天的一次买入至多只能购买ASi股,一次卖出至多只能卖 出BSi股。
另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔W 天,也就是说如果在第i天发生了交易,那么从第i+1天到第i+W天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的 股票数不能超过MaxP。
在第1天之前,lxhgww手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,T天以后,lxhgww想要赚到最多的钱,聪明的 程序员们,你们能帮助他吗?

Input

输入数据第一行包括3个整数,分别是T,MaxP,W。
接下来T行,第i行代表第i-1天的股票走势,每行4个整数,分别表示APi,BPi,ASi,BSi。

Output

输出数据为一行,包括1个数字,表示lxhgww能赚到的最多的钱数。

Sample Input

5 2 0
2 1 1 1
2 1 1 1
3 2 1 1
4 3 1 1
5 4 1 1

Sample Output

3

Hint

【数据范围】
对于30%的数据,0<=W<T<=50,1<=MaxP<=50
对于50%的数据,0<=W<T<=2000,1<=MaxP<=50
对于100%的数据,0<=W<T<=2000,1<=MaxP<=2000
对于所有的数据,1<=BPi<=APi<=1000,1<=ASi,BSi<=MaxP 【算法分析】
该题很容易得出方程和状态:
F[i,j]表示第i天,手上有j点股份,所得的最大收入。
然后预处理从第i天开始买的情况。
即F[i,j]=-buyprize[i]*j   其中 j<=buylimit[i]。
然后是转移。
转移分两种,一种是卖,一种是买。
F[i,j]=max{
1、F[i-1,j] 就是第i天不买股份,手上依然j点股份。
2、F[i-w-1,k] – buyprize[i]*(j-k) 其中 1<=j-k<=buylimit[i]   表示今天买j-k那么多股份。
3、F[i-w-1,k] + sellprize[i]*(k-j) 其中 1<=k-j<=selllimit[i]   表示今天卖k-j这么多股份。
}
然后第一种方案直接转移。然后后面两种,显然可以用单调队列优化。
最终时间复杂度O(T*maxp)
【CODE】
#include #include #include const int N=2005;
int T,maxp,W;
int inp[N],outp[N],inlimit[N],outlimit[N];
int F[N][N];
struct queue{int p,f;}q[N];

void input(){
scanf("%d%d%d",&T,&maxp,&W);
for (int i=1;i<=T;i++)
scanf("%d%d%d%d",&inp[i],&outp[i],&inlimit[i],&outlimit[i]);
}

inline int min(int x,int y){return xinline int max(int x,int y){return x>y?x:y;}

void dp(){
int i,j,k,h,t;
int *pf,*f;
memset(F,200,sizeof(F));
for (i=1;i<=T;i++){
F[i][0]=0;
for (j=1;j<=min(inlimit[i],maxp);j++) F[i][j]=-inp[i]*j;
}
for (i=2;i<=T;i++){
pf=F[i-W-1];
f=F[i];
for (j=0;j<=maxp;j++) F[i][j]=max(F[i][j],F[i-1][j]);
if (i pf=F[i-W-1];
f=F[i];
h=t=0; q[0].p=0; q[0].f=pf[0];
for (j=1;j<=maxp;j++){
while (h<=t && j-q[h].p>inlimit[i]) h++;
if (h<=t) f[j]=max(f[j],q[h].f+(q[h].p-j)*inp[i]);
while (h<=t && q[t].f+(q[t].p-j)*inp[i] t++;
q[t].p=j;
q[t].f=pf[j];
}
h=t=0; q[0].p=maxp; q[0].f=pf[maxp];
for (j=maxp-1;j>=0;j–){
while (h<=t && q[h].p-j>outlimit[i]) h++;
if (h<=t) f[j]=max(f[j],q[h].f+(q[h].p-j)*outp[i]);
while (h<=t && q[t].f+(q[t].p-j)*outp[i] t++;
q[t].p=j;
q[t].f=pf[j];
}
}
}

void output(){
int ans=0;
for (int i=0;i<=maxp;i++)
ans=max(ans,F[T][i]);
printf("%dn",ans);
}

int main(){
freopen("trade.in","r",stdin);
freopen("trade.out","w",stdout);
input();
dp();
output();
}

[SCOI2010 第一试 游戏]二分图匹配 or 并差集

【题目】

Description

lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示。当他使用某种装备 时,他只能使用该装备的某一个属性。并且每种装备最多只能使用一次。
游戏进行到最后,lxhgww遇到了终极boss,这个终极boss很奇怪,攻击他的装备所使用的属性值必须从1开始连续递增地攻击,才能对boss产生 伤害。也就是说一开始的时候,lxhgww只能使用某个属性值为1的装备攻击boss,然后只能使用某个属性值为2的装备攻击boss,然后只能使用某个 属性值为3的装备攻击boss……以此类推。
现在lxhgww想知道他最多能连续攻击boss多少次?

Input

输入的第一行是一个整数N,表示lxhgww拥有N种装备
接下来N行,是对这N种装备的描述,每行2个数字,表示第i种装备的2个属性值

Output

输出一行,包括1个数字,表示lxhgww最多能连续攻击的次数。

Sample Input

3
1 2
3 2
4 5

Sample Output

2

Hint

对于30%的数据,保证N<=1000
对于100%的数据,保证N<=1000000 【算法分析】
做法1:
这是通法~
我们把那个10000个数字放在二分图的左边,然后把武器放在二分图的右边。
对于一个武器,连左边两个对应的点。
然后从左边的点1开始一直像正常的匹配那样搞下去,直到某一个地方,找不到增广路,证明前i个点不能被同时满足了。那么输出i-1即可。

做法2:
这个我觉得并不容易想到。
把那10000个点看成点。
然后武器看成无向边。
(1)对于一个联通块,假如不含环(就是一棵树),那么必定可以满足其中任意的p-1个点。
(2)对于一个联通块,假如含环,那么必定全部的p个点都能满足。
如此用并差集维护即可。
对(1)容易用数学归纳法证明。
对(2),可以通过(1)来证明。
【其他】
实际测试表明,匹配和并差集的速度差不多。
注:并差集用的IO外挂,所以速度会快很多,实际上去掉以后速度是差不多的。
【CODE——匹配】
#include #include #include const int N=1000005;
struct gtp{int y,next;}g[N*2];
int n,e;
int a[N*2][2],v[N*2],ls[10005],link[N*2];

inline void addedge(int x,int y){
e++;
g[e].y=y; g[e].next=ls[x]; ls[x]=e;
}

bool find(int p,int kk){
int q;
for (int t=ls[p];t;t=g[t].next)
if (v[g[t].y]!=kk){
v[g[t].y]=kk;
q=link[g[t].y];
link[g[t].y]=p;
if (!q || find(q,kk)) return true;
link[g[t].y]=q;
}
return false;
}

void solve(){
e=0; memset(ls,0,sizeof(ls));
for (int i=1;i<=n;i++){
addedge(a[i][0],i);
addedge(a[i][1],i);
}
for (int i=1;i<=10001;i++){
if (!find(i,i)){
printf("%dn",i-1);
return;
}
}
}

int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d%d",&a[i][0],&a[i][1]);
solve();
}

【CODE——并差集】
#include #include #include const int N=10005;
int n;
int f[N],v[N];
char ch;

int gf(int p){
if (f[p]==p) return p;
return f[p]=gf(f[p]);
}

inline void Read(int &x){
x=0;
ch=getchar();
while (ch==’ ‘ || ch==’n’) ch=getchar();
while (ch!=’ ‘ && ch!=’n’){
x=x*10+ch-‘0’;
ch=getchar();
}
}

int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
int x,y,i,tmp;
for (i=1;i<=N;i++){
f[i]=i;
v[i]=0;
}
Read(n);
for (i=1;i<=n;i++){
Read(x); Read(y);
if (gf(x)!=gf(y)){
if (f[x]>f[y]){tmp=x; x=y; y=tmp;}
v[f[y]]|=v[f[x]];
f[f[x]]=f[y];
}else v[f[x]]=1;
}
for (i=1;i if (f[i]==i && !v[i]){
printf("%dn",i-1);
break;
}
}

[SCOI2010 第一试 幸运数字]容斥原理

【题目】

Description

  在中国,很多人都把6和8视为是幸运数字!lxhgww也这样认为,于是他定义自己的“幸运号码”是十进制表示中只包含数字6和8的那些号码,比如 68,666,888都是“幸运号码”!但是这种“幸运号码”总是太少了,比如在[1,100]的区间内就只有6个(6,8,66,68,86,88), 于是他又定义了一种“近似幸运号码”。lxhgww规定,凡是“幸运号码”的倍数都是“近似幸运号码”,当然,任何的“幸运号码”也都是“近似幸运号 码”,比如12,16,666都是“近似幸运号码”。
现在lxhgww想知道在一段闭区间[a, b]内,“近似幸运号码”的个数。

Input

输入数据是一行,包括2个数字a和b

Output

输出数据是一行,包括1个数字,表示在闭区间[a, b]内“近似幸运号码”的个数

Sample Input

【样例输入1】
1 10
【样例输入2】
1234 4321

Sample Output

【样例输出1】
2
【样例输出2】
809

Hint

对于30%的数据,保证1<=a<=b<=1000000
对于100%的数据,保证1<=a<=b<=10000000000 【算法分析】
该题使用容斥原理,把符合的数先都DFS出来,然后把互为约数的,大的那个删掉。
最后需要注意各种不要超范围。
因为题目给的范围异常猥琐,b*b都会爆long long,所以需要各种先除后乘。
【其他】
由于之前因为程序中注释处溢出一直TLE,然后围观与标程有什么不同,以至于已经和标程没啥区别了
= =。。。Orz。。。
【CODE】
#include #include #include #include using namespace std;
typedef long long lld;
const int N=20000;
lld a,b,tot=0,ans,X;
lld list[N];

void makelist(int dep,lld x){
if (x>b) return;
list[tot++]=x;
makelist(dep+1,x*10+6);
makelist(dep+1,x*10+8);
}

lld gcd(lld x,lld y){
lld z;
while (y){
z=x%y;
x=y;
y=z;
}
return x;
}

void dfs(int st,int times,lld num){
int i; lld g;
for (i=st;i>=1;i–){
g=gcd(list[i],num);
if (num/g<=X/list[i]){ // 改成if (num/g*list[i]<=X)的话 ,会因溢出而TLE在那里!
if (times&1) ans-=X/(num/g*list[i]);
else ans+=X/(num/g*list[i]);
dfs(i-1,times+1,num/g*list[i]);
}
}
}

lld cal(lld x){
if (x<=5) return 0;
ans=0; X=x;
dfs(tot-1,0,1);
return ans;
}

int main(){
freopen("luckynumber.in","r",stdin);
freopen("luckynumber.out","w",stdout);
cin >> a >> b;
makelist(0,0);
sort(list,list+tot);
for (int i=2;i bool flag=false;
for (int j=1;j if (list[i]%list[j]==0){
flag=true;
break;
}
if (flag){
for (int j=i;j+1 list[j]=list[j+1];
tot–;
i–;
}
}
cout << cal(b)-cal(a-1) << endl;
}