文章目录
- T1.排水系统
- 题目描述
- 思路
- 代码
- T2.字符串匹配
- 题目描述
- 思路
- 代码
- T3.移球游戏
- 题目描述
- 思路
- 代码
- T4.微信步数
- 题目描述
对于一个城市来说,排水系统是极其重要的一个部分。
有一天,小 C 拿到了某座城市排水系统的设计图。排水系统由 n n n 个排水结点(它们从 1 ∼ n 1 \sim n 1∼n 编号)和若干个单向排水管道构成。每一个排水结点有若干个管道用于汇集其他排水结点的污水(简称为该结点的汇集管道),也有若干个管道向其他的排水结点排出污水(简称为该结点的排出管道)。
排水系统的结点中有 m m m 个污水接收口,它们的编号分别为 1 , 2 , … , m 1, 2, \ldots , m 1,2,…,m ,污水只能从这些接收口流入排水系统,并且这些结点没有汇集管道。排水系统中还有若干个最终排水口,它们将污水运送到污水处理厂,没有排出管道的结点便可视为一个最终排水口。
现在各个污水接收口分别都接收了 1 1 1 吨污水,污水进入每个结点后,会均等地从当前结点的每一个排出管道流向其他排水结点,而最终排水口将把污水排出系统。
现在小 C 想知道,在该城市的排水系统中,每个最终排水口会排出多少污水。该城市的排水系统设计科学,管道不会形成回路,即不会发生污水形成环流的情况。
STEP1 拓扑排序
“管道不会形成回路”表示这是一张有向无环图 DAG 。于是结合“每一个排水结点有若干个管道用于汇集其他排水结点的污水”这一条件便很容易想到拓扑排序的思路(事实上也可以用搜索的思路)。先把 m m m 个污水接收口的污水量初始化为 1 1 1 ,再在每轮找到一个入度为零的排水节点,把它有的污水平均分配到它所连的其它排水节点内,最后把所有与它相连的结点入度减一就行了。注意入度为零的排水节点不一定是污水接收口!
STEP2 高精
然而这只能拿到 60 60 60 的好成绩,这题需要高精。但注意到约分后分子 p p p 比分母 q q q 往往小得多,分子只需要unsigned long long
就行了。注意到每个节点的排出管道不超过五个,不大于五的质数只有 2 2 2、 3 3 3、 5 5 5 这三个,分母 q q q 便可以以 p = 2 a 3 b 5 c p=2^a3^b5^c p=2a3b5c 的形式存储,输出答案时只需要高精快速幂就行了。
STEP3 分数加法
假设现在有两个分数 p 1 q 1 \frac{p_1}{q_1} q1p1 和 p 2 q 2 \frac{p_2}{q_2} q2p2 要相加,则有 p 1 q 1 + p 2 q 2 = p 1 q 2 + p 2 q 1 q 1 q 2 \frac{p_1}{q_1}+\frac{p_2}{q_2}=\frac{p_1q_2+p_2q_1}{q_1q_2} q1p1+q2p2=q1q2p1q2+p2q1
然而这样会面临着分子爆unsigned long long
的风险,因此需要同时约分。我们考虑所有包含 2 2 2 的引子,则原式可化为 2 a 2 x 1 + 2 a 1 x 2 2 a 1 + a 2 x 3 \frac{2^{a_2}x_1+2^{a_1}x_2}{2^{a_1+a_2}x_3} 2a1+a2x32a2x1+2a1x2 的形式。不妨设 a 1 ≤ a 2 a_1\le a_2 a1≤a2,则上下可以约掉 2 a 1 2^{a_1} 2a1 ,即化为 原 式 = 2 a 1 ( 2 a 2 − a 1 x 1 + x 2 ) 2 a 1 ( 2 a 2 x 3 ) = 2 a 2 − a 1 x 1 + x 2 2 a 2 x 3 原式=\frac{2^{a_1}(2^{a_2-a_1}x_1+x_2)}{2^{a_1}(2^{a_2}x_3)}=\frac{2^{a_2-a_1}x_1+x_2}{2^{a_2}x_3} 原式=2a1(2a2x3)2a1(2a2−a1x1+x2)=2a2x32a2−a1x1+x2这样就能够巧妙地解决精度问题。对于 3 3 3 和 5 5 5 也有类似讨论,这里不再赘述。
#include
#include
#define ll unsigned long long
using namespace std;
int n,m,to[100001][6],rd[100001];
int stk[100001],top;
ll pw[6][1001];
ll qpw(ll x,int y){
//记忆化求幂,所有求幂复杂度总共是线性的
if(pw[x][y]==0) return pw[x][y]=qpw(x,y-1)*x;
else return pw[x][y];
}
struct num{
//分母
int a,b,c;
num(){
a=b=c=0;
return;
}
};
struct big_num{
//高精
int x[51],len;
void operator=(ll a){
len=-1;
while(a) x[++len]=a%10,a/=10;
return;
}
void write(){
for(int i=len;i>=0;i-=1) printf("%d",x[i]);
printf("\n");
return;
}
};
big_num operator*(big_num a,big_num b){
//高精乘
big_num c;
int z=0,l,r;
c.len=a.len+b.len;
for(int i=0;i<=c.len;i+=1){
c.x[i]=z;
l=max(0,i-b.len); r=min(i,a.len);
for(int j=l;j<=r;j+=1){
c.x[i]+=a.x[j]*b.x[i-j];
}
z=c.x[i]/10;
c.x[i]%=10;
}
while(z) c.x[++c.len]=z%10,z/=10;
return c;
}
struct frac{
//分数
ll p;
num q;
void operator=(ll x){
p=x;
return;
}
void deal(){
//约分
while(q.a&&p%2llu==0llu){
p/=2llu;
q.a-=1;
}
while(q.b&&p%3llu==0llu){
p/=3llu;
q.b-=1;
}
while(q.c&&p%5llu==0llu){
p/=5llu;
q.c-=1;
}
return;
}
void write(){
big_num x,y;
x=qpw(2ll,q.a); y=qpw(3ll,q.b);
x=x*y; y=qpw(5ll,q.c); x=x*y;
printf("%llu ",p);
x.write();
return;
}
}f[100001];
frac operator/(frac x,int y){
//均分
if(y==2) x.q.a+=1;
if(y==3) x.q.b+=1;
if(y==4) x.q.a+=2;
if(y==5) x.q.c+=1;
x.deal(); //约分
return x;
}
frac operator+(frac x,frac y){
//分数加
frac z;
ll v=x.p,w=y.p;
z.p=0llu;
if(x.q.a>=y.q.a) w*=qpw(2llu,x.q.a-y.q.a);
else v*=qpw(2llu,y.q.a-x.q.a);
if(x.q.b>=y.q.b) w*=qpw(3llu,x.q.b-y.q.b);
else v*=qpw(3llu,y.q.b-x.q.b);
if(x.q.c>=y.q.c) w*=qpw(5llu,x.q.c-y.q.c);
else v*=qpw(5llu,y.q.c-x.q.c);
z.p=v+w;
z.q.a=max(x.q.a,y.q.a);
z.q.b=max(x.q.b,y.q.b);
z.q.c=max(x.q.c,y.q.c);
z.deal(); //约分
return z;
}
int main(){
// freopen("water.in","r",stdin);
// freopen("water.out","w",stdout);
int x,y;
pw[2][0]=pw[3][0]=pw[5][0]=1llu;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i+=1){
if(i<=m) f[i]=1llu; //初始水量
else f[i]=0llu;
scanf("%d",&to[i][0]);
for(int j=1;j<=to[i][0];j+=1){
scanf("%d",&to[i][j]);
rd[to[i][j]]+=1;
}
}
for(int i=1;i<=n;i+=1){
if(!rd[i]) stk[++top]=i;
}
while(top){
//拓扑排序
x=stk[top--];
if(to[x][0]) f[x]=f[x]/to[x][0];
for(int i=1;i<=to[x][0];i+=1){
y=to[x][i];
rd[y]-=1;
if(!rd[y]) stk[++top]=y;
f[y]=f[x]+f[y]; //累计
}
}
int cnt=0;
for(int i=1;i<=n;i+=1){
if(!to[i][0]) f[i].write(); //输出
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
小 C 学习完了字符串匹配的相关内容,现在他正在做一道习题。
对于一个字符串 S S S,题目要求他找到 S S S 的所有具有下列形式的拆分方案数:
S = A B C S = ABC S=ABC, S = A B A B C S = ABABC S=ABABC, S = A B A B … A B C S = ABAB \ldots ABC S=ABAB…ABC,其中 A A A, B B B, C C C 均是非空字符串,且 A A A 中出现奇数次的字符数量不超过 C C C 中出现奇数次的字符数量。
更具体地,我们可以定义 A B AB AB 表示两个字符串 A A A, B B B 相连接,例如 A = aab A = \texttt{aab} A=aab, B = ab B = \texttt{ab} B=ab,则 A B = aabab AB = \texttt{aabab} AB=aabab。
并递归地定义 A 1 = A A^1=A A1=A, A n = A n − 1 A^n = A^{n - 1} An=An−1( n ≥ 2 n \ge 2 n≥2 且为正整数)。例如 A = abb A = \texttt{abb} A=abb,则 A 3 = abbabbabb A^3=\texttt{abbabbabb} A3=abbabbabb
则小 C 的习题是求 S = ( A B ) i C S = {(AB)}^iC S=(AB)iC 的方案数,其中 F ( A ) ≤ F ( C ) F(A) \le F(C) F(A)≤F(C), F ( S ) F(S) F(S) 表示字符串 S S S 中出现奇数次的字符的数量。两种方案不同当且仅当拆分出的 A A A、 B B B、 C C C 中有至少一个字符串不同。
小 C 并不会做这道题,只好向你求助,请你帮帮他。
假设 S S S 下标从 1 1 1 开始,长度为 l e n len len,即 S [ 1 … l e n ] S[1\dots len] S[1…len]。
STEP1 直接暴力
这个思路其实非常好想。首先从头枚举 i i i 表示 A B = S [ 1 … i ] AB=S[1\dots i] AB=S[1…i],再枚举可能的出现的次数 j j j,这里可以用字符串哈希,那么 C = S [ i ∗ j + 1 … l e n ] C=S[i*j+1\dots len] C=S[i∗j+1…len]。最后找到所有的 A = S [ 1 … k ] A=S[1\dots k] A=S[1…k]( 1 ≤ k < i 1\le k< i 1≤k<i)满足 F ( A ) ≤ F ( C ) F(A)\le F(C) F(A)≤F(C) 并累计到答案中就行了。注意到拆分出来的第一个字符串和最后一个字符串分别是 A A A 和 C C C,便可以提前以 O ( 2 ∗ l e n ) O(2*len) O(2∗len) 的复杂度处理好 F F F 数组。
STEP2 特殊性质
若最大次数为 j m a x j_{max} jmax , D D D 为剩下的字符串,则 S = ( A B ) j m a x D S=(AB)^{j_{max}}D S=(AB)jmaxD。不难发现 C C C 可以写成 ( A B ) j m a x − j D (AB)^{j_{max}-j}D (AB)jmax−jD 的形式。
不难发现,两个 A B AB AB 平在一起后所有出现奇数次的字符都被抵消,换句话说, F ( ( A B ) 2 ) = 0 F((AB)^2)=0 F((AB)2)=0,那么就可以得到 F ( D ) = F ( ( A B ) 2 D ) = f ( ( A B ) 4 D ) = … F(D)=F((AB)^2D)=f((AB)^4D)=\dots F(D)=F((AB)2D)=f((AB)4D)=… 和 F ( A B D ) = F ( ( A B ) 3 D ) = F ( ( A B ) 5 D ) = … F(ABD)=F((AB)^3D)=F((AB)^5D)=\dots F(ABD)=F((AB)3D)=F((AB)5D)=…。因此我们只要统计 C = D C=D C=D 和 C = A B D C=ABD C=ABD 的答案,并分别乘上 F F F 相同的情况数就行了。
STEP3 树状数组
我们发现,统计符合条件的 A A A 时,还需要枚举 k k k,这就大大降低了效率。但我们要求的是 F F F 不大于某个值的前缀字符串个数。令 c n t [ i ] [ j ] cnt[i][j] cnt[i][j] 表示枚举到当前位置 i i i 时,满足 F ( S [ 1 … k ] ≤ i ) F(S[1\dots k]\le i) F(S[1…k]≤i) 且 1 ≤ k ≤ i 1\le k\le i 1≤k≤i 的 k k k 的个数。在每一轮时,初始化 c n t [ i ] [ j ] = c n t [ i − 1 ] [ j ] cnt[i][j]=cnt[i-1][j] cnt[i][j]=cnt[i−1][j] 如果当前有 F ( S [ 1 … i ] ) = j F(S[1\dots i])=j F(S[1…i])=j,则把 c n t [ i ] [ j … 26 ] cnt[i][j\dots 26] cnt[i][j…26] 都加上 1 1 1 就行了。不难发现,这个数组的前一维可以去掉。进一步的,把 c n t [ j … 26 ] cnt[j\dots 26] cnt[j…26] 这段区间都加上 1 1 1 可以看成把它的差分数组第 j j j 位加上 1 1 1,便可以用树状数组来求前缀和。
#include
#include
#include
#define ull unsigned long long
using namespace std;
ull base=131llu,h[1050000],pow;
char s[1050000];
int t,len,k,tot;
ull ans,cnt[1050001];
int f[1050000],isodd[26];
int lowbit(int x){
return x&(-x);
}
void update(int x){
//单点加一
for(x;x<=27;x+=lowbit(x)) cnt[x]+=1;
return;
}
int sum(int x){
//前缀和
int res=0;
for(x;x;x-=lowbit(x)) res+=cnt[x];
return res;
}
int main(){
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
scanf("%d",&t);
while(t--){
ans=0llu;
scanf("%s",s+1);
len=strlen(s+1);
for(int i=0;i<26;i+=1) isodd[i]=cnt[i+1]=0;
for(int i=1;i<=len+1;i+=1) f[i]=1; //多组数据初始化
for(int i=len;i>=1;i-=1){
isodd[s[i]-'a']^=1;
f[i]=f[i+1]+(isodd[s[i]-'a']? 1:-1); //F(S[i...len])
}
for(int i=1;i<=len;i+=1) h[i]=h[i-1]*base+s[i]-'a';
for(int i=0;i<26;i+=1) isodd[i]=0;
isodd[s[1]-'a']=1;
update(tot=2);
//树状数组下标不能为零,tot表示当前出现奇数次字符个数加一
pow=base*base;
for(int i=2;i<=len-1;i+=1){
k=1;
for(int j=i*2;j<=len-1;j+=i){
//求出最大循环次数
if(h[j]!=h[j-i]*pow+h[i]) break;
k+=1;
}
ans+=(k+1llu)/2llu*sum(f[i*k+1]); //两种情况
ans+=k/2llu*sum(f[i*(k-1)+1]);
isodd[s[i]-'a']^=1;
tot+=(isodd[s[i]-'a']? 1llu:-1llu);
update(tot); //更新cnt
pow*=base;
}
printf("%lld\n",ans); //输出答案
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
小 C 正在玩一个移球游戏,他面前有 n + 1 n + 1 n+1 根柱子,柱子从 1 ∼ n + 1 1 \sim n + 1 1∼n+1 编号,其中 1 1 1 号柱子、 2 2 2 号柱子、……、 n n n 号柱子上各有 m m m 个球,它们自底向上放置在柱子上, n + 1 n + 1 n+1 号柱子上初始时没有球。这 n × m n \times m n×m 个球共有 n n n 种颜色,每种颜色的球各 m m m 个。
初始时一根柱子上的球可能是五颜六色的,而小 C 的任务是将所有同种颜色的球移到同一根柱子上,这是唯一的目标,而每种颜色的球最后放置在哪根柱子则没有限制。
小 C 可以通过若干次操作完成这个目标,一次操作能将一个球从一根柱子移到另一根柱子上。更具体地,将 x x x 号柱子上的球移动到 y y y 号柱子上的要求为:
小 C 的目标并不难完成,因此他决定给自己加加难度:在完成目标的基础上,使用的操作次数不能超过 820000 820000 820000。换句话说,小 C 需要使用至多 820000 820000 820000 次操作完成目标。
小 C 被难住了,但他相信难不倒你,请你给出一个操作方案完成小 C 的目标。合法的方案可能有多种,你只需要给出任意一种,题目保证一定存在一个合法方案。
这道题只需要找到一种移球的方法,按照它模拟就好了。下面提供其中一种思路。
我们先看到 n = 2 n=2 n=2 的情况。比方说有下面这种情况:
我们以 c n t cnt cnt 表示 1 1 1 号柱子上颜色为 1 1 1 的球的个数。那么显然有 c n t = 3 cnt=3 cnt=3。于是先把 2 2 2 号柱子上的前 c n t cnt cnt 个球移动到 3 3 3 号柱子上,就得到了下图。
接着就从上往下依次考虑 1 1 1 号柱子上的球,如果颜色为 1 1 1 就把它移到 2 2 2 号柱子上去;否则移到 3 3 3 号柱子上去。
于是我们就会发现 2 2 2 号柱子上前 c n t cnt cnt 个球都是 1 1 1, 3 3 3 号柱子上前 m − c n t m-cnt m−cnt 个球都是 2 2 2。接着我们就把 2 2 2 号柱子上的前 c n t cnt cnt 个球和 3 3 3 号柱子上的前 m − c n t m-cnt m−cnt 个球按类别移回 1 1 1 号柱子上,接着把 3 3 3 号柱子剩下的球都移到 2 2 2 号柱子上。
到这里我们实际上是给 1 1 1 号柱子上球排个序。接着我们把 1 1 1 号柱子上的两种类型的球分开到 1 1 1 号和 3 3 3 号柱子上。
接下来只要把 2 2 2 号柱子上的球分开就行了。
对于 n ≥ 3 n\ge 3 n≥3 的情况,我们每一轮把同种颜色的球移到一起,问题就转变成颜色总数为 n − 1 n-1 n−1 的子问题。我们把当前要聚集的颜色看作 1 1 1 其它都看作零。于是两步就可以完成每一轮移动。
STEP1 构造全零列
对于下面这种情况,记录 c n t cnt cnt 为当前 1 1 1 号柱子上颜色为 1 1 1 的球的个数,则有 c n t = 2 cnt=2 cnt=2。
类似于上面 n = 2 n=2 n=2 的讨论,我们也可以把 1 1 1 中的 1 1 1 和 0 0 0 利用 3 3 3 号柱子和 4 4 4 号柱子分开(标红的是原 1 1 1 号柱子上的球):
接着我们把 4 4 4 号柱子上的 m − c n t m-cnt m−cnt 个编号为零的球移到 1 1 1 号柱子上去:
我们再考虑 2 2 2 号柱子。把其中编号为零的球移到 1 1 1 号柱子上直到填满为止,剩下的球都移动到 4 4 4 号柱子上。
如图所示(标红的是原 2 2 2 号柱子上的球),第一列就是一个全零列。事实上,由于原来 1 1 1 号和 2 2 2 号柱子上为零的球的个数必定大于 m ( 1 号 柱 子 ) + m ( 2 号 柱 子 ) − m ( 最 大 可 能 有 编 号 为 1 的 球 的 个 数 ) = m m(1号柱子)+m(2号柱子)-m(最大可能有编号为1的球的个数)=m m(1号柱子)+m(2号柱子)−m(最大可能有编号为1的球的个数)=m,也就是说必能构造全零列。
STEP2 构造全一列
#include
#include
using namespace std;
int n,m,ans,ansx[820001],ansy[820001];
int a[52][401],p[52],cnt;
void move(int x,int y){
a[y][++a[y][0]]=a[x][a[x][0]--];
if(ansx[ans]==y&&ansy[ans]==x) ans-=1;
else ansx[++ans]=x,ansy[ans]=y;
return;
}
int chk(int x,int y){
int res=0;
for(int i=1;i<=a[x][0];i+=1) res+=(a[x][i]==y);
return res;
}
int main(){
// freopen("ball.in","r",stdin);
// freopen("ball.out","w",stdout);
scanf("%d%d",&n,&m);
p[n+1]=n+1;
for(int i=1;i<=n;i+=1){
a[i][0]=m; p[i]=i;
for(int j=1;j<=m;j+=1) scanf("%d",&a[i][j]);
}
for(int i=n;i>=3;i-=1){
cnt=chk(p[1],i);
for(int j=1;j<=cnt;j+=1){
move(p[i],p[i+1]);
}
for(int j=m;j>=1;j-=1){
if(a[p[1]][j]==i) move(p[1],p[i]);
else move(p[1],p[i+1]);
}
for(int j=1;j<=m-cnt;j+=1) move(p[i+1],p[1]);
for(int j=m;j>=1;j-=1){
if(a[p[2]][j]!=i&&a[p[1]][0]<m) move(p[2],p[1]);
else move(p[2],p[i+1]);
}
swap(p[2],p[i+1]); swap(p[1],p[i]);
for(int j=1;j<i;j+=1){
cnt=chk(p[j],i);
for(int k=1;k<=cnt;k+=1) move(p[i],p[i+1]);
for(int k=m;k>=1;k-=1){
if(a[p[j]][k]==i) move(p[j],p[i]);
else move(p[j],p[i+1]);
}
swap(p[j],p[i]); swap(p[i],p[i+1]);
}
for(int j=1;j<i;j+=1){
for(int k=m;k>=1;k-=1){
if(a[p[j]][k]==i) move(p[j],p[i+1]);
else break;
}
while(a[p[j]][0]<m) move(p[i],p[j]);
}
}
cnt=chk(p[1],1);
for(int i=1;i<=cnt;i+=1) move(p[2],p[3]);
for(int i=m;i>=1;i-=1){
if(a[p[1]][i]==1) move(p[1],p[2]);
else move(p[1],p[3]);
}
for(int i=1;i<=cnt;i+=1) move(p[2],p[1]);
for(int i=1;i<=m-cnt;i+=1) move(p[3],p[1]);
for(int i=1;i<=cnt;i+=1) move(p[3],p[2]);
for(int i=1;i<=m-cnt;i+=1) move(p[1],p[3]);
for(int i=m;i>=1;i-=1){
if(a[p[2]][i]==1) move(p[2],p[1]);
else move(p[2],p[3]);
}
printf("%d\n",ans);
for(int i=1;i<=ans;i+=1) printf("%d %d\n",ansx[i],ansy[i]);
// fclose(stdin);
// fclose(stdout);
return 0;
}
小 C 喜欢跑步,并且非常喜欢在微信步数排行榜上刷榜,为此他制定了一个刷微信步数的计划。
他来到了一处空旷的场地,处于该场地中的人可以用 k k k 维整数坐标 ( a 1 , a 2 , … , a k ) (a_1, a_2, \ldots , a_k) (a1,a2,…,ak) 来表示其位置。场地有大小限制,第 i i i 维的大小为 w i w_i wi ,因此处于场地中的人其坐标应满足 1 ≤ a i ≤ w i 1 \le a_i \le w_i 1≤ai≤wi( 1 ≤ i ≤ k 1 \le i \le k 1≤i≤k)。
小 C 打算在接下来的 P = w 1 × w 2 × ⋯ × w k P = w_1 \times w_2 \times \cdots \times w_k P=w1×w2×⋯×wk 天中,每天从场地中一个新的位置出发,开始他的刷步数计划(换句话说,他将会从场地中每个位置都出发一次进行计划)。
他的计划非常简单,每天按照事先规定好的路线行进,每天的路线由 n n n 步移动构成,每一步可以用 c i c_i ci 与 d i d_i di 表示:若他当前位于 ( a 1 , a 2 , … , a c i , … , a k ) (a_1, a_2, \ldots , a_{c_i}, \ldots, a_k) (a1,a2,…,aci,…,ak),则这一步他将会走到 ( a 1 , a 2 , … , a c i + d i , … , a k ) (a_1, a_2, \ldots , a_{c_i} + d_i, \ldots , a_k) (a1,a2,…,aci+di,…,ak),其中 1 ≤ c i ≤ k 1 \le c_i \le k 1≤ci≤k, d i ∈ { − 1 , 1 } d_i \in \{-1, 1\} di∈{ −1,1}。小 C 将会不断重复这个路线,直到他走出了场地的范围才结束一天的计划。(即走完第 n n n 步后,若小 C 还在场内,他将回到第 1 1 1 步从头再走一遍)。
小 C 对自己的速度非常有自信,所以他并不在意具体耗费的时间,他只想知道 P P P 天之后,他一共刷出了多少步微信步数。请你帮他算一算。
预祝大家来今后的比赛中取得优异的成绩!
谢谢观看!