[SCOI 第二试 传送带]二分法 or 二分+暴力 or 直接暴力

【题目】

Description

在一个2维平面上有两条传送带,每一条传送带可以看成是一条线段。两条传送带分别为线段AB和线段CD。lxhgww在AB上的移动速度为P,在CD上的 移动速度为Q,在平面上的移动速度R。现在lxhgww想从A点走到D点,他想知道最少需要走多长时间

Input

输入数据第一行是4个整数,表示A和B的坐标,分别为Ax,Ay,Bx,By
第二行是4个整数,表示C和D的坐标,分别为Cx,Cy,Dx,Dy
第三行是3个整数,分别是P,Q,R

Output

输出数据为一行,表示lxhgww从A点走到D点的最短时间,保留到小数点后2位

Sample Input

0 0 0 100
100 0 100 100
2 2 1

Sample Output

136.60

Hint

对于100%的数据,1<= Ax,Ay,Bx,By,Cx,Cy,Dx,Dy<=1000
1<=P,Q,R<=10 【算法分析】
好吧,很容易得出路径应该是这样一种形式:
A->p1->p2->D
其中p1在线段AB上,p2在线段CD上。
然后感觉应该是都满足单峰性质的。
表示不会证明。。。只是感觉。
可以用r1 * AB向量和r2 * DC向量分别表示p1、p2。

第一种:
嵌套二分r1和r2。得到速度非常快的一个算法。

第二种:
如果不放心,那么枚举r1,精度卡一下,卡1e-5之类。
然后二分r2。
得到一个比较快的算法。

第三种:
如果还不放心,那么直接暴力它吧。经测试,直接枚举r1和r2,卡精度2e-4,可以在每个测试数据1.8s的情况下暴力AC!
果然,很黄很暴力~

【其他】
我是傻X,因为求的是最小值,而不是最大值。
一开始二分的部分应该l=mid的时候写成r=mid
应该r=mid的时候写成了l=mid。
导致以为不符合单峰性质,所以有了各种暴力尝试。。。

【CODE1】
#include #include #include #include #include using namespace std;
const double eps=1e-6;
int Ax,Ay,Bx,By,Cx,Cy,Dx,Dy,Rab,Rcd,R;
double xx1,yy1,xx2,yy2,d1,d2,d3,r1;

inline double dis(double x1,double y1,double x2,double y2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

inline double Get(double r2){
xx1=(Bx-Ax)*r1+Ax;
yy1=(By-Ay)*r1+Ay;
xx2=(Cx-Dx)*r2+Dx;
yy2=(Cy-Dy)*r2+Dy;
d1=dis(xx1,yy1,Ax,Ay)/Rab;
d2=dis(xx1,yy1,xx2,yy2)/R;
d3=dis(xx2,yy2,Dx,Dy)/Rcd;
return d1+d2+d3;
}

double solve(double tmp){
r1=tmp;
double l=0,r=1,mid;
while (l+eps mid=(l+r)/2;
if (Get(mid) else l=mid;
}
return Get(l);
}

int main(){
cin>>Ax>>Ay>>Bx>>By>>Cx>>Cy>>Dx>>Dy>>Rab>>Rcd>>R;
double l=0,r=1,mid;
while (l+eps mid=(l+r)/2;
if (solve(mid) else l=mid;
}
printf("%.2lfn",solve(l));
}
【CODE2】
#include #include #include #include #include using namespace std;
const double eps=1e-5;
int Ax,Ay,Bx,By,Cx,Cy,Dx,Dy,Rab,Rcd,R;
double xx1,yy1,xx2,yy2,d1,d2,d3,r1;

inline double dis(double x1,double y1,double x2,double y2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

inline double Get(double r2){
xx1=(Bx-Ax)*r1+Ax;
yy1=(By-Ay)*r1+Ay;
xx2=(Cx-Dx)*r2+Dx;
yy2=(Cy-Dy)*r2+Dy;
d1=dis(xx1,yy1,Ax,Ay)/Rab;
d2=dis(xx1,yy1,xx2,yy2)/R;
d3=dis(xx2,yy2,Dx,Dy)/Rcd;
return d1+d2+d3;
}

double solve(double tmp){
r1=tmp;
double l=0,r=1,mid;
while (l+eps mid=(l+r)/2;
if (Get(mid) else l=mid;
}
return Get(l);
}

int main(){
cin>>Ax>>Ay>>Bx>>By>>Cx>>Cy>>Dx>>Dy>>Rab>>Rcd>>R;
double i=0,ans=1e20;
for (i=0;i<1;i+=eps)
ans=min(ans,solve(i));
printf("%.2lfn",ans);
}
【CODE3】
#include #include #include #include #include using namespace std;
const double eps=1e-6;
const double eps2=1e-7;
const double eeps=2e-4;
int Ax,Ay,Bx,By,Cx,Cy,Dx,Dy,Rab,Rcd,R;

inline double dis(double x1,double y1,double x2,double y2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

inline double Get(double r1,double r2){
double x1,y1,x2,y2,d1,d2,d3;
x1=(Bx-Ax)*r1+Ax;
y1=(By-Ay)*r1+Ay;
x2=(Cx-Dx)*r2+Dx;
y2=(Cy-Dy)*r2+Dy;
d1=dis(x1,y1,Ax,Ay)/Rab;
d2=dis(x1,y1,x2,y2)/R;
d3=dis(x2,y2,Dx,Dy)/Rcd;
return d1+d2+d3;
}

double solve(double r1){
double l,ans=1e20;
for (l=0;l<1;l+=eeps) ans=min(ans,Get(r1,l));
return ans;
}

int main(){
cin>>Ax>>Ay>>Bx>>By>>Cx>>Cy>>Dx>>Dy>>Rab>>Rcd>>R;
double l=0,r=1,mid,ans=1e20;
for (l=0;l<1;l+=eeps) ans=min(ans,solve(l));
printf("%.2lfn",ans);
}

[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;
}
}