Clam and Fish
有鱼的话贪心捉鱼.
否则,有饲料则选饲料.
若 t y p e = 0 type=0 type=0且有饲料,则捉鱼.
最后剩余的饲料数/2即为可以多捉的鱼.(即前面为饲料,后面为鱼)
int n,ans,tot;
char s[N];
int main() {
int _;qr(_); while(_--) {
qr(n); ans=tot=0; scanf("%s",s+1);
for(int i=1;i<=n;i++) {
int c=s[i]-'0';
if(c&2) ans++;
else if(c&1) tot++;
else if(tot) ans++,tot--;
}
pr2(ans+tot/2);
}
return 0;
}
Classical String Problem
如果我们把这个序列看作一个环的话,那么这个环的结构不变.
此时我们只需要记录原序列的第一个位置现在在哪里即可.
int n,m,pos;
char s[N];
int main() {
scanf("%s",s); n=strlen(s);
for(int i=0;i<n;i++) s[i+n]=s[i];
qr(m); while(m--) {
char op[5]; int x;
scanf("%s",op); qr(x);
if(op[0]=='M') pos+=x,pos=(pos+n)%n;
else putchar(s[pos+x-1]),puts("");
}
return 0;
}
Operation Love
观察到只有大拇指的左边两点的距离为6,所以我们可以以他为参照点.
然后假如所有的点都在他们的右边则为右手.
否则为左手.
int n=20;
struct P {
double x,y;
P(double x=0,double y=0):x(x),y(y){};
P operator -(P b) const {return P(x-b.x,y-b.y);}
double operator *(P b) const {return x*b.y-b.x*y;}
double dis(P b) const {return sqrt((x-b.x)*(x-b.x)+(y-b.y)*(y-b.y));}
bool pd(P b,int x) const {return fabs(x-dis(b))<eps;}
} a[N];
double mult(P a,P b,P c) {return (a-c)*(b-c);}
int main() {
int _;qr(_); while(_--) {
for(int i=1;i<=n;i++) {
double x,y;
scanf("%lf%lf",&x,&y);
a[i]=P(x,y);
}
a[n+1]=a[1]; a[n+2]=a[2];
int x=0,y;
for(int i=1;i<=n;i++)
if(a[i].pd(a[i+1],6)) {
if(a[i+1].pd(a[i+2],1)) x=i,y=i+1;
else x=i+1,y=i;//x为掌心内侧的点.
break;
}
bool flag=1;
for(int i=1;i<=n;i++)
if(i^x&&i^y&&mult(a[x],a[y],a[i])>0) flag=0;
if(flag) puts("right");
else puts("left");
}
return 0;
}
Points Construction Problem
先贪心弄出最小的答案(接近正方形).
然后缓慢增加答案,把从后往前塞到第一行的末尾.
如果还达不到,那么我们只好让一些点孤立了~~
int n,m,len,ans;
struct P {int x,y;} a[N];
void out() {
puts("Yes");
for(int i=1;i<=n;i++) pr1(a[i].x),pr2(a[i].y);
}
int main() {
int _;qr(_); while(_--) {
qr(n); qr(m); len=sqrt(n+0.1); ans=0;
if(m&1) {puts("No"); continue;}
//设水平投影和竖直投影长度分别为a,b,则ans=2(a+b).要令ans最小就必须造出一个接近正方形的东西.
int x=1,y=1;
for(int i=1;i<=n;i++) {
a[i]=(P){x,y++};
if(y>len) x++,y=1;
}
ans=2*(x-(y==1)+len);
if(m<ans||m>4*n) {puts("No"); continue;}
x=1; y=len+1;
if(m==ans) {out(); continue;}
for(int i=n;i>len;i--) {//不够就把后面的拆到第一行,实现缓慢增长.
ans+=(a[i].y>1)*2;
a[i]=(P){x,y++};
if(ans==m)break;
}
if(ans==m) {out(); continue;}
//如果还不够,那么我们考虑把块扔出.这样就会把贡献由2增加到4
x=y=1;
for(int i=1;i<=n;i++) {
a[i]=(P){x,y++};
if(ans<m) x++,ans+=2;
}
out();
}
return 0;
}
Two Matchings
很自然地想到要sort一下,然后交替求差为最小值.
然后我们考虑把序列按每4个或6个划分.
我们定义长度为n的划分的旋转为 n − r o t a t e n-rotate n−rotate.
那么 n = 4 , a = 1 , 2 , 3 , 4 , n − r o t a t e = 2 , 3 , 4 , 1 n=4,a=1,2,3,4,n-rotate=2,3,4,1 n=4,a=1,2,3,4,n−rotate=2,3,4,1
n = 6 , a = 1 , 2 , 3 , 4 , 5 , 6 , n − r o t a t e = 2 , 3 , 4 , 5 , 6 , 1 n=6,a=1,2,3,4,5,6,n-rotate=2,3,4,5,6,1 n=6,a=1,2,3,4,5,6,n−rotate=2,3,4,5,6,1
可以发现这样和不旋转的配合是贡献最小的,代价和为 a [ n ] − a [ 1 ] a[n]-a[1] a[n]−a[1].
如果是 n = 2 k ( k > 3 ) n=2k(k>3) n=2k(k>3)的话显然我们把它划分成更小的 n n n,可以使得中间的部分差不求,实现更小.
所以不难得到一个dp方程 f [ i ] = m a x ( f [ i − 4 ] + a [ i ] − a [ i − 3 ] , f [ i − 6 ] , a [ i ] − a [ i − 5 ] ) f[i]=max(f[i-4]+a[i]-a[i-3],f[i-6],a[i]-a[i-5]) f[i]=max(f[i−4]+a[i]−a[i−3],f[i−6],a[i]−a[i−5]).
最后 a n s = f [ n ] ∗ 2 ans=f[n]*2 ans=f[n]∗2.
int n,m,f[N],a[N];
int main() {
int _;qr(_); while(_--) {
qr(n);
for(int i=1;i<=n;i++) qr(a[i]);
sort(a+1,a+n+1);
f[2]=1e9;f[4]=(a[4]-a[1]);
for(int i=6;i<=n;i+=2)
f[i]=min(f[i-4]+a[i]-a[i-3],f[i-6]+a[i]-a[i-5]);
pr2(f[n]*2);
}
return 0;
}
Fraction Construction Problem
已知 a , b ( a , b > 0 ) a,b(a,b>0) a,b(a,b>0),求 c , d , e , f ( c , d , e , f > 0 ) c,d,e,f(c,d,e,f>0) c,d,e,f(c,d,e,f>0),使得 c d − e f = a b \dfrac{c}{d}-\dfrac{e}{f}=\dfrac a b dc−fe=ba.
显然如果 t = gcd ( a , b ) > 1 t=\gcd(a,b)>1 t=gcd(a,b)>1,我们直接 c = ( a + b ) / t , d = b / t , e = f = 1 c=(a+b)/t,d=b/t,e=f=1 c=(a+b)/t,d=b/t,e=f=1即可.
否则, c f − e d d f = a b \dfrac{cf-ed}{df}=\dfrac ab dfcf−ed=ba.
我们令 d f = b ( d ⊥ f ) df=b(d\bot f) df=b(d⊥f),根据裴蜀定理可知一定有解.
这个我们可以用线性筛求 d , f d,f d,f.
然后exgcd一下就好了.
int prime[N],tot,P[N]; bool v[N];
void get() {
P[0]=P[1]=1;
for(int i=2;i<N;i++) {
if(!v[i]) prime[++tot]=i,P[i]=i;
for(int j=1,k;(k=i*prime[j])<N;j++) {
v[k]=1;
if(i%prime[j]==0) {P[k]=P[i]*prime[j]; break;}
P[k]=prime[j];
}
}
}
void exgcd(ll a,ll b,ll &x,ll &y) {
if(!a) {x=y=1; return ;}
exgcd(b%a,a,y,x); x-=b/a*y;
}
int main() {
int T; qr(T); get(); while(T--) {
ll a,b,c,d,e,f,t;
qr(a); qr(b); t=gcd(a,b);
if(t>1) {
c=(a+b)/t; d=b/t; e=f=1;
pr1(c); pr1(d); pr1(e); pr2(f);
continue;
}
d=P[b]; f=b/d;
if(f==1) {puts("-1 -1 -1 -1"); continue;}
exgcd(f,d,c,e); e=-e;
t=max(-c/d,-e/f)+1;
if(t>0) c+=t*d,e+=t*f;
c *= a ; e *= a;
pr1(c); pr1(d); pr1(e); pr2(f);
}
return 0;
}
Operating on a Graph
并查集 + l i s t +list +list合并.显然 l i s t list list的总大小始终 ≤ 2 m \le 2m ≤2m.
介绍一下 l i s t list list的合并吧: ′ l i s t 1. s p l i c e ( p o s , l i s t 2 ) ′ 'list1.splice(pos,list2)' ′list1.splice(pos,list2)′,表示把 l i s t 2 list2 list2放到 p o s pos pos迭代器之前的位置,并清空 l i s t 2 list2 list2.
int n,m,fa[N],vis[N],num;
list<int> e[N];
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}
int main() {
int T; qr(T); while(T--) {
qr(n); qr(m);
for(int i=0;i<n;i++) fa[i]=i,e[i].clear();
for(int i=0,x,y;i<m;i++)
qr(x),qr(y),e[x].pb(y),e[y].pb(x);
qr(m); while(m--) {
int x; qr(x);
if(get(x)^x) continue;
int sz=SZ(e[x]);
vis[x]=++num;
while(sz--) {
int y=e[x].front(); e[x].pop_front();
if(vis[y=get(y)]==num) continue;//这个团已经被合并
e[x].splice(e[x].end(),e[y]);
fa[y]=x; vis[y]=num;
}
}
for(int i=0;i<n;i++) pr1(get(i));
puts("");
}
return 0;
}
不知为啥 v e c t o r vector vector合并要更快?
Sort the Strings Revision
我们可以用st表预处理连续一段变化的最小值,然后用归并排序sort.复杂度为 O ( n log n ) O(n\log n) O(nlogn),可以得到60分的好成绩…
#include<bits/stdc++.h>
#define R register
using namespace std;
typedef long long ll;
const int N=2e6+10;
void qr(int &x) {scanf("%d",&x);}
void qr(ll &x) {scanf("%lld",&x);}
int T,n,lg[N],p[N],d[N],f[N][24],r[N],ans[N],tmp[N],s[N];
ll seed,a,b,mod;
inline int Min(const int A,const int B) {return A<=B?A:B;}
inline bool cmp(int x,int y) {
int k=lg[y-x];
int pos=Min(f[x][k],f[y-(1<<k)][k]);
return pos==n || s[pos]<d[pos];
}
inline void Sort(int l,int r) {
if(l==r) return ;
int mid=(l+r)/2;
Sort(l,mid);
Sort(mid+1,r);
for(R int i=l,j=mid+1,k=l;k<=r;k++)
tmp[k]=(j>r|| (i<=mid&&cmp(ans[i],ans[j])))?ans[i++]:ans[j++];
for(int i=l;i<=r;i++) ans[i]=tmp[i];
// printf("%d %d:\n",l,r);
// for(int i=l;i<=r;i++) printf("%d ",ans[i]);
// puts("");
}
int main() {
s[1]=1;
for(int i=2;i<N;i++) lg[i]=lg[i>>1]+1,s[i]=i%10;
qr(T); while(T--) {
qr(n);
qr(seed); qr(a); qr(b); qr(mod);
for(int i=0;i<n;i++) p[i]=i;
for(int i=1;i<n;i++)
swap(p[seed%(i+1)],p[i]),
seed=(seed*a+b)%mod;
qr(seed); qr(a); qr(b); qr(mod);
for(int i=0;i<n;i++)
d[p[i]]=seed%10,
seed=(seed*a+b)%mod;
for(int i=0;i<n;i++)
if(s[p[i]]==d[p[i]]) f[i][0]=n;
else f[i][0]=p[i];
for(int j=1;j<=lg[n];j++)
for(int i=0;i+(1<<j)<=n;i++)
f[i][j]=Min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for(int i=0;i<=n;i++) ans[i]=i;
Sort(0,n);
for(int i=0;i<=n;i++) r[ans[i]]=i;
b=10000019; mod=1000000007;
ll res=0;
for(R int i=n;i>=0;i--) res=(res*b+r[i])%mod;
printf("%lld\n",res%mod);
}
}
其实我们每次只用找到序列
p
p
p的最小值
p
k
p_k
pk.
假设
p
k
≠
d
k
p_k\ne d_k
pk=dk.
那么
s
0
.
.
.
s
k
s_0...s_k
s0...sk的第
k
k
k位和
s
k
+
1
.
.
.
s
n
s_{k+1}...s_n
sk+1...sn一定不同.
我们可以根据这一位划分出两块的字典序大小,简单差分实现即可.
对于
p
k
=
d
k
p_k=d_k
pk=dk的情况,我们直接忽略即可.
所以我们只要对
p
k
≠
d
k
p_k\ne d_k
pk=dk的位置建一棵笛卡尔树,然后分治处理即可.总复杂度为
O
(
n
)
O(n)
O(n).
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+10;
void qr(int &x) {scanf("%d",&x); }
void qr(ll &x) {scanf("%lld",&x);}
int T,n,p[N],c[N],lc[N],rc[N],sta[N],top,d[N];
ll seed,a,b,mod;
void solve(int x,int l,int r) {
if(x==-1) return ;
solve(lc[x],l,x);
solve(rc[x],x+1,r);
if(p[x]%10<d[x]) c[x+1]+=x-l+1,c[r+1]-=x-l+1;
else c[l]+=r-x,c[x+1]-=r-x;
}
int main() {
qr(T); while(T--) {
qr(n);
qr(seed); qr(a); qr(b); qr(mod);
for(int i=0;i<n;i++) p[i]=i;
for(int i=1;i<n;i++)
swap(p[seed%(i+1)],p[i]),
seed=(seed*a+b)%mod;
qr(seed); qr(a); qr(b); qr(mod);
for(int i=0;i<n;i++)
d[i]=seed%10,
seed=(seed*a+b)%mod;
for(int i=0;i<=n;i++) c[i]=0,lc[i]=rc[i]=-1;
top=0;
for(int i=0;i<n;i++) {
if(p[i]%10!=d[i]) {
while(top&&p[sta[top]]>p[i]) lc[i]=sta[top--];
if(top) rc[sta[top]]=i;
sta[++top]=i;
}
}
if(top) solve(sta[1],0,n);
int tot=0;
for(int i=0;i<=n;i++) {
c[i] += c[i-1];
if(i&&p[i-1]%10==d[i-1])
sta[i]=c[i]+(++tot);
else sta[i]=c[i],tot=0;
// printf("%d ",sta[i]);
}
a=10000019; mod=1000000007; ll res=0;
// for(int i=0;i<=n;i++) printf("%d ",p[i]);
for(int i=n;i>=0;i--) res=(res*a+sta[i])%mod;
printf("%lld\n",res);
}
}
Sorting the Array
好题.
观察1:
r
e
t
[
i
]
ret[i]
ret[i]为
[
1
,
i
+
m
−
1
]
[1,i+m-1]
[1,i+m−1]除去
r
e
t
[
1...
(
i
−
1
)
]
ret[1...(i-1)]
ret[1...(i−1)]的最小值. 可以看作我们在维护一个大小为
m
−
1
m-1
m−1的集合,每次操作加入一个数,并删除最小值.
观察2: 如果
j
<
i
,
r
e
t
[
j
]
>
r
e
t
[
i
]
j<i,ret[j]>ret[i]
j<i,ret[j]>ret[i],则
a
[
i
+
m
−
1
]
=
r
e
t
[
i
]
a[i+m-1]=ret[i]
a[i+m−1]=ret[i].证明:在从
j
j
j到
i
i
i的过程中,凡是加入一个
<
r
e
t
[
i
]
<ret[i]
<ret[i]的数都会被删除,所以不会留到
i
i
i,所以可以确定
a
[
i
+
m
−
1
]
=
r
e
t
[
i
]
a[i+m-1]=ret[i]
a[i+m−1]=ret[i].
观察3:如果我们把满足观察2的
i
i
i全部
d
e
l
e
t
e
delete
delete,得到一个上升序列,设剩余元素有
t
t
t个,那么我们可以把剩余元素离散化成
[
1
,
t
]
[1,t]
[1,t],并且由于在求
r
e
t
[
i
]
ret[i]
ret[i]时,集合中的数一定不是已经被
d
e
l
e
t
e
delete
delete的数.
设
g
(
n
,
a
)
=
∣
{
b
∣
f
(
n
,
b
,
m
)
=
a
}
∣
g(n,a)=|\{b|f(n,b,m)=a \}|
g(n,a)=∣{b∣f(n,b,m)=a}∣,则有
g
(
n
,
a
)
=
g
(
t
,
{
1
,
2
,
.
.
.
.
,
t
}
)
g(n,a)=g(t,\{1,2,....,t\})
g(n,a)=g(t,{1,2,....,t})
观察4: 进行完转化后,对于数
i
i
i而言,可以放的位置为
[
1
,
min
(
i
+
m
−
1
,
t
)
]
[1,\min(i+m-1,t)]
[1,min(i+m−1,t)],由于前面的数的可放区间更小,所以在放完前
i
−
1
i-1
i−1个数后,数
i
i
i的方案数为
d
[
i
]
=
min
(
m
,
t
−
i
+
1
)
d[i]=\min(m,t-i+1)
d[i]=min(m,t−i+1).
具体实现:
我们从后往前枚举,求有多少位不确定.(更具体的,求满足
s
u
m
=
∏
i
=
x
t
d
[
i
]
≥
k
sum=\prod_{i=x}^t d[i]\ge k
sum=∏i=xtd[i]≥k 的最大的
x
x
x)
然后我们前面的位(
[
1
,
x
−
1
]
[1,x-1]
[1,x−1])都是取最小----
a
n
s
[
i
]
=
i
ans[i]=i
ans[i]=i.
然后我们对于每一位
i
i
i 从小到大考虑该放什么(
j
j
j),
我们令
s
u
m
=
∏
i
=
x
t
d
[
i
]
sum=\prod_{i=x}^t d[i]
sum=∏i=xtd[i].
那么当扫到
j
j
j 时,以后
j
j
j 的合法选择就减小了1.(我们要证
i
≤
j
+
m
−
1
↔
i
−
(
m
−
1
)
≤
j
,
i
−
(
m
−
1
)
i\le j+m-1\leftrightarrow i-(m-1)\le j,i-(m-1)
i≤j+m−1↔i−(m−1)≤j,i−(m−1)为第
i
i
i位最小的值,所以一定成立).
由于 m ≤ 2 m\le 2 m≤2,所以后面至多有 log k \log k logk位,暴力就是 O ( n log 2 k ) O(n\log ^2 k) O(nlog2k).
int n,m,ans[N],a[N],b[N],t,id[N];
bool vis[N];
ll k,sum;
int main() {
int _; qr(_); while(_--) {
qr(n); qr(m); qr(k); t=0;
int mx=0;
for(int i=1;i<=n;i++) ans[i]=vis[i]=0;
for(int i=1,j=1;i<=n;i++) {
qr(a[i]);
if(a[i]<mx) ans[i+m-1]=a[i];
else {
mx=a[++t]=a[i];
while(ans[j]) j++;
id[t]=j++;
}
}
int last=t; sum=1;
while(sum<k) {
last--;
sum=sum*min(t-last+1,m);
}
for(int i=1;i<last;i++) ans[id[i]]=a[i];
for(int i=last;i<=t;i++) b[i]=min(m,t-i+1);
for(int i=last;i<=t;i++)
for(int j=last;j<=t;j++) {
if(!vis[j]) {
sum /= b[j]--;
if(k<=sum) {
vis[j]=1;
ans[id[i]]=a[j];
break;
}
k -= sum;
sum *= b[j];
}
}
for(int i=1;i<=n;i++) pr1(ans[i]);
puts("");
}
}