【前言】
没有第七场,因为是本校出题
最后rk66,校3/9
1001. X-liked Counting
【题意】
问 [ l , r ] [l,r] [l,r]有多少个数字,满足它的某个前缀或后缀能被 x x x整除。
l , r ≤ 1 0 18 , x ≤ 500 l,r\leq 10^{18},x\leq 500 l,r≤1018,x≤500
【思路】
直接计算前缀或后缀中至少有一个满足条件的总数不太方便,考虑补集,计算没有一个前缀和后缀满足条件的再用总数减去。
由于前缀和后缀之间有重合,不能直接在头尾两端转移。观察到 x x x很小,不妨先枚举整个数对 x x x取模的值,再从前后任选一个方向转移,并记录当前前后缀对 x x x取模的结果,这样前缀和后缀模 x x x的结果都可以表示出来,主要注意没有前导零的细节。
算总状态数很多,但实际上有用状态很少,剪枝就可以过了。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
int T;
long long l,r;
int x;
long long dp[30][1010][2][2];
int a[30];
int p10[30];
long long p9[30];
long long solve(long long n)
{
for(int i=1;i<=19;i++)a[i]=n%10,n/=10;
long long res=1;
for(int i=19;i>=1;i--)
{
int cnt=0;
for(int j=0;j<a[i];j++)cnt+=(j!=7);
res+=p9[i-1]*cnt;
if(a[i]==7)break;
}
for(int i=1;i<=19;i++)
{
if(a[i]==7)
{
res--;
break;
}
}
for(int t=1;t<x;t++)
{
for(int i=0;i<=19;i++)for(int j=0;j<x;j++)dp[i][j][0][0]=dp[i][j][0][1]=dp[i][j][1][0]=dp[i][j][1][1]=0;
dp[19][0][1][1]=1;
for(int i=19;i>=1;i--)
{
for(int j=0;j<x;j++)
{
for(int k=0;k<2;k++)
{
for(int l=0;l<2;l++)
{
if(!dp[i][j][k][l])continue;
int mx=k?a[i]:9;
for(int d=0;d<=mx;d++)
{
if(d==7)continue;
int nj=(j*10+d)%x;
if(d || !l)if(!nj || (i>1 && nj*p10[i-1]%x==t))continue;
dp[i-1][nj][k&(d==mx)][l&(!d)]+=dp[i][j][k][l];
}
}
}
}
}
res-=dp[0][t][0][0]+dp[0][t][1][0];
}
return res;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld%d",&l,&r,&x);
p10[0]=1%x;p9[0]=1;
for(int i=1;i<=19;i++)p10[i]=p10[i-1]*10%x,p9[i]=9LL*p9[i-1];
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}
1002. Buying Snacks
【题意】
有 n n n种物品,每个物品有大小两类,大的2元,小的1元。现在要买其中的一些,但是每种物品只能买至多一件。同时,可以将相邻两个编号的物品一起买,使得它们两个总价减去1元。问 ∀ k ∈ [ 1 , m ] \forall k\in [1,m] ∀k∈[1,m]花费 k k k元买的物品有多少种方案。答案对998244353取模。
n ≤ 1 0 9 , m ≤ 20000 n\leq 10^9,m\leq 20000 n≤109,m≤20000
【思路】
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i个物品,一共用了
j
j
j块的方案数,有如下的转移方程:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
f
[
i
−
1
]
[
j
−
1
]
+
f
[
i
−
1
]
[
j
−
2
]
+
f
[
i
−
2
]
[
j
−
1
]
+
2
f
[
i
−
2
]
[
j
−
2
]
+
f
[
i
−
2
]
[
j
−
3
]
f[i][j]=f[i-1][j]+f[i-1][j-1]+f[i-1][j-2]+f[i-2][j-1]+2f[i-2][j-2]+f[i-2][j-3]
f[i][j]=f[i−1][j]+f[i−1][j−1]+f[i−1][j−2]+f[i−2][j−1]+2f[i−2][j−2]+f[i−2][j−3]
意义显然
设
g
i
g_i
gi表示
f
i
f_i
fi的生成函数,那么:
g
i
=
(
x
2
+
x
+
1
)
g
i
−
1
+
(
x
3
+
2
x
2
+
x
)
g
i
−
2
g_i=(x^2+x+1)g_{i-1}+(x^3+2x^2+x)g_{i-2}
gi=(x2+x+1)gi−1+(x3+2x2+x)gi−2
众所周知,常系数线性齐次递推可以用矩阵乘法加速,而这个东西同样可以用矩阵乘法做。
复杂度 O ( 8 ⋅ m log m log n ) O(8\cdot m\log m\log n) O(8⋅mlogmlogn),初始值 g 0 = 1 , g 1 = x 2 + x + 1 g_0=1,g_1=x^2+x+1 g0=1,g1=x2+x+1
这个复杂度有点卡,所以需要比较优秀的NTT板子,或者在快速幂的时候不进行idft。
然后众所周知,常系数线性齐次递推还有一种BM+CH的做法,这个常数会小很多。
然后其实别的队推导后(打表后)发现答案是 ∑ ( − 1 ) i ( 2 n − 2 i k − i ) \sum (-1)^i \binom{2n-2i}{k-i} ∑(−1)i(k−i2n−2i),所以 O ( m 2 ) O(m^2) O(m2)就能过。
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=7e4+5,mod=998244353;
ll inv[maxn],fac[maxn],ifac[maxn];
inline ll add(ll x,ll y){x+=y;return x%mod;}
inline ll mul(ll x,ll y){return (ll)x*y%mod;}
inline ll ksm(ll x,ll k){ll ans=1;for(;k;k>>=1,x=x*x%mod)if(k&1)(ans*=x)%=mod;return ans;}
inline void init_inv(int n){inv[1]=1;rep(i,2,n) inv[i]=mul(mod-mod/i,inv[mod%i]);}
inline void init_fac(int n){fac[0]=ifac[0]=1;
rep(i,1,n) fac[i]=mul(fac[i-1],i),ifac[i]=mul(ifac[i-1],inv[i]);
}
struct FT{
int n,nn; ll w[2][maxn],rev[maxn],tmp;
inline int Init(int _n){
for(n=1;n<=_n;n<<=1); if(n==nn) return n;nn=n;
ll w0=ksm(3,(mod-1)/n);
w[0][0]=w[1][0]=1;
rep(i,1,n-1) w[0][i]=w[1][n-i]=mul(w[0][i-1],w0);
rep(i,0,n-1) rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
return n;
}
void FFT(ll a[],int op){
rep(i,0,n-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<n;i<<=1){
for(int j=0,t=n/(i<<1);j<n;j+=(i<<1)){
for(int k=j,l=0;k<j+i;k++,l+=t){
ll x=a[k],y=mul(w[op][l],a[k+i]);
a[k]=add(x,y);a[k+i]=add(x-y,mod);
}
}
}
if(op) {tmp=inv[n];rep(i,0,n) a[i]=mul(a[i],tmp);}
}
}ft;
int n,m,len;
ll c[maxn],d[maxn],tp[maxn];
void print(ll *x){cout<<"(";rep(i,0,m-1) cout<<x[i]<<",)"[i==m-1];}
void mul(ll *a,ll *b,ll *c){
rep(i,0,m-1) c[i]=a[i],d[i]=b[i];
rep(i,m,len) c[i]=d[i]=0;
ft.FFT(c,0);ft.FFT(d,0);
rep(i,0,len-1) c[i]=mul(c[i],d[i]);
ft.FFT(c,1);
rep(i,m,len) c[i]=0;
}
ll res[2][2][maxn],ans[2][2][maxn],tmp[2][2][maxn];
ll v[2][maxn],X[maxn],Y[maxn];
void KSM(int p){
rep(i,0,1)rep(j,0,1){
rep(k,0,len) ans[i][j][k]=0;
if(i==j) ans[i][j][0]=1;
}
for(;p;p>>=1){
if(p&1){
rep(i,0,1)rep(j,0,1){
rep(k,0,len) tmp[i][j][k]=0;
rep(k,0,1){
mul(res[i][k],ans[k][j],tp);
rep(t,0,m-1) tmp[i][j][t]=add(tmp[i][j][t],tp[t]);
}
}
rep(i,0,1)rep(j,0,1)rep(t,0,m-1) ans[i][j][t]=tmp[i][j][t];
}
rep(i,0,1)rep(j,0,1){
rep(k,0,len) tmp[i][j][k]=0;
rep(k,0,1){
mul(res[i][k],res[k][j],tp);
rep(t,0,m-1) tmp[i][j][t]=add(tmp[i][j][t],tp[t]);
}
}
rep(i,0,1)rep(j,0,1)rep(t,0,m-1) res[i][j][t]=tmp[i][j][t];
}
}
int main(){
freopen("data.in","r",stdin);
freopen("my.out","w",stdout);
init_inv(maxn-5);init_fac(maxn-5);
dwn(_,yh(),1){
n=yh(),m=yh()+1;
len=ft.Init(2*m-1);
rep(i,0,1)rep(j,0,1){
rep(t,0,len) ans[i][j][t]=res[i][j][t]=0;
}
rep(i,0,1)rep(t,0,len) v[i][t]=0;
res[0][0][0]=1;res[0][0][1]=1;res[0][0][2]=1;
res[0][1][1]=1;res[0][1][2]=2;res[0][1][3]=1;
res[1][0][0]=1;
v[0][0]=1;v[0][1]=1;v[0][2]=1;
v[1][0]=1;
KSM(n-1);
mul(ans[0][0],v[0],X);mul(ans[0][1],v[1],Y);
rep(i,1,m-1){
printf("%lld ",add(X[i],Y[i]));
}
putchar(10);
}
cerr<<clock();
return 0;
}
1003. Ink on paper
【题目】
n n n片墨水,初始位置在 ( x i , y i ) (x_i,y_i) (xi,yi),初始半径可以看作0,每秒半径增加0.5,问多久这些墨水连成一片。
n ≤ 5000 n\leq 5000 n≤5000
【思路】
显然就是完全图的MST,用没有堆优化的Prim即可。
复杂度 O ( n 2 ) O(n^2) O(n2)
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
const ll inf=(ll)3e18;
const int N=5005;
int n;
int vis[N],a[N],b[N];
ll dis[N][N],nowdis[N];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
/*struct node
{
int v;ll w;
node(int v=0,ll w=0):v(v),w(w){}
bool operator < (const node&rhs)const
{
return w>rhs.w;
}
};
priority_queue<node>q;*/
ll prim()
{
//int cnt=0;
ll ans=0;
// while(!q.empty()) q.pop();
for(int i=1;i<=n;++i) vis[i]=0;
vis[1]=1;
for(int i=2;i<=n;++i)
nowdis[i]=dis[1][i];
for(int i=1;i<n;++i)
{
ll now=inf;int id=0;
for(int j=1;j<=n;++j) if(!vis[j])
{
//printf("%d %lld %lld\n",j,now,nowdis[j]);
if(now>nowdis[j]) now=nowdis[j],id=j;
//printf("%d %lld\n",j,nowdis[j]);
}
vis[id]=1;
//printf("!!%d\n",id);
for(int j=1;j<=n;++j) if(!vis[j])
nowdis[j]=min(nowdis[j],dis[id][j]);
ans=max(ans,now);
}
/* q.push(node(1,0));
while(!q.empty() && cnt<=n)
{
node x=q.top();q.pop();
if(vis[x.v]) continue;
vis[x.v]=1;ans=max(ans,x.w);
++cnt;
for(int i=1;i<=n;++i) if(!vis[i])
q.push(node(i,dis[x.v][i]));
}*/
return ans;
}
ll sqr(ll x){return x*x;}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d",&a[i],&b[i]);
for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j)
{
ll w=sqr(a[i]-a[j])+sqr(b[i]-b[j]);
dis[i][j]=dis[j][i]=w;
//e[i].pb(node(j,w));e[j].pb(node(i,w));
}
printf("%lld\n",prim());
}
return 0;
}
1004. Counting Stars
【题意】
一个长度为 n n n的序列 a i a_i ai,有三种操作:
-
∀ i ∈ [ l , r ] \forall i\in[l,r] ∀i∈[l,r]每个数字减去 a i & ( − a i ) a_i\&(-a_i) ai&(−ai)
-
∀ i ∈ [ l , r ] \forall i \in [l,r] ∀i∈[l,r],每个数字加上一个 2 popcount ( a i ) 2^{\text{popcount}(a_i)} 2popcount(ai), popcount ( a i ) \text{popcount}(a_i) popcount(ai)表示 a i a_i ai二进制最高位的1是哪一位。
-
询问区间数字和
n , q ≤ 1 0 5 , a i ≤ 1 0 9 n,q\leq 10^5,a_i\leq 10^9 n,q≤105,ai≤109
【思路】
第一个操作相当于去掉最低位的1,第二个操作相当于最高位的1往更高位移动。
用线段树来维护,那么显然对于一个数字我们最多进行 log \log log次第一个操作,这部分就是经典的势能分析,直接暴力递归修改。第二个操作相当于给区间加2的最高位的次方和,我们可以通过将最高位和低位分别维护,这个操作可以看成是区间乘2。
复杂度 O ( n log A + q log n ) O(n\log A+q\log n) O(nlogA+qlogn),反正是一个 log \log log
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret;scanf("%d",&ret);return ret;
}
const int maxn=1e5+5,mod=998244353;
struct num{int x; int high;}a[maxn];
int sum[maxn<<2];
int lz[maxn<<2],high[maxn<<2];
ll pw[maxn];
#define ls (v<<1)
#define rs (v<<1|1)
#define mid ((l+r)>>1)
void push_up(int v){
sum[v]=(sum[ls]+sum[rs])%mod;
high[v]=(high[ls]+high[rs])%mod;
}
void opt(int v,int tms){
sum[v]=(sum[v]+(ll)high[v]*(pw[tms]-1))%mod;
high[v]=((ll)high[v]*pw[tms]%mod)%mod;
lz[v]+=tms;
}
void push_down(int v){
if(lz[v]){
opt(ls,lz[v]);opt(rs,lz[v]);lz[v]=0;
}
}
int al,ar;
int ans;
void add(int v,int l,int r){
if(al<=l&&ar>=r) return opt(v,1);
push_down(v);
if(al<=mid) add(ls,l,mid); if(ar>mid) add(rs,mid+1,r);
push_up(v);
}
void qry(int v,int l,int r){
if(al<=l&&ar>=r) return ans=(ans+sum[v])%mod,void();
push_down(v);
if(al<=mid) qry(ls,l,mid); if(ar>mid) qry(rs,mid+1,r);
}
void clear(int v,int l,int r){
sum[v]=high[v]=0;if(l==r)return;
push_down(v);
clear(ls,l,mid);clear(rs,mid+1,r);
}
void dlt(int v,int l,int r){
if(high[v]==0) return;
if(al<=l&&ar>=r&&high[v]==sum[v]) return clear(v,l,r);
if(l==r){
if(a[l].x) sum[v]=(sum[v]-(a[l].x&-a[l].x)+mod)%mod,a[l].x-=a[l].x&(-a[l].x);
else sum[v]=high[v]=a[l].high=0;
return;
}
push_down(v);
if(al<=mid) dlt(ls,l,mid); if(ar>mid) dlt(rs,mid+1,r);
push_up(v);
}
void build(int v,int l,int r){
sum[v]=high[v]=lz[v]=0;
if(l==r){
int y=yh(); dwn(i,30,0)if((y>>i)&1){
a[l].x=(y^(1<<i));
a[l].high=(1<<i);
sum[v]=y; high[v]=(1<<i)%mod;
break;
}
return;
}
build(ls,l,mid);build(rs,mid+1,r);
push_up(v);
}
int n;
int main(){
// #ifdef van
// freopen("my.in","r",stdin);
// #endif
pw[0]=1;rep(i,1,maxn-1) pw[i]=(pw[i-1]*2ll)%mod;
dwn(_,yh(),1){
n=yh();
build(1,1,n);
dwn(m,yh(),1){
int op=yh(); al=yh(),ar=yh();
if(op==2)dlt(1,1,n);
else if(op==3)add(1,1,n);
else{
ans=0; qry(1,1,n);
printf("%d\n",ans);
}
// rep(i,1,n){
// ans=0; al=ar=i; qry(1,1,n);cout<<ans<<" \n"[i==n];
// }
}
}
return 0;
}
1005. Separated Number
【题意】
一个长度为 n n n的数字,问把它分成至多 k k k个连续段的所有情况的每段数字和之和。
n , k ≤ 1 0 6 n,k\leq 10^6 n,k≤106
【思路】
一个很显然的暴力是枚举一个段,然后考虑这个段贡献多少次计算权值。
然后可以发现这个段 [ l , r ] [l,r] [l,r]贡献多少次,事实上是考虑在这个段的左右选择多少个右端点,其中 l − 1 l-1 l−1和 n n n一定要选,那么这个显然是枚举选择的个数后用组合数计算。然后又可以发现除了开头和结尾的段,长度相等的串贡献次数是相同,于是考虑一个长度怎么计算贡献。
先不管开头结尾串,这个很容易算。
设当前考虑的是
l
e
n
len
len,这样前后一共有
r
e
s
=
n
−
l
e
n
−
2
res=n-len-2
res=n−len−2个位置可以任选(注意
l
−
1
l-1
l−1和
n
n
n一定要选),那么总的次数就是:
∑
i
=
0
min
(
k
−
3
,
r
e
s
)
(
r
e
s
i
)
\sum_{i=0}^{\min(k-3,res)}\binom{res}{i}
i=0∑min(k−3,res)(ires)
这个组合数在
r
e
s
≤
k
−
3
res\leq k-3
res≤k−3就是杨辉三角的一层,答案是
2
r
e
s
2^{res}
2res
而当 r e s > k − 3 res>k-3 res>k−3,实际上是杨辉三角某层的前缀,这个东西是可以从 2 r e s 2^{res} 2res开始递推的:设当前层权值为 n o w now now,那么下一层就是 2 × n o w − ( r e s K − 3 ) 2\times now-\binom{res}{K-3} 2×now−(K−3res)(即考虑每个数对下一层贡献两次,然后第 K − 2 K-2 K−2个数是贡献1次)
开头和结尾的串贡献类似,这里不复述了。
最后一个问题是处理出每个长度的串权值和,这个东西就求出前缀数字和,然后从 s u m [ 1 ] sum[1] sum[1]开始简单递推就行。
复杂度 O ( n ) O(n) O(n)
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
const int N=1e6+10,mod=998244353;
namespace Math
{
int fac[N],ifac[N],inv[N],bas[N],pw[N];
int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
void up(int &x,int y){x=upm(x+y);}
int mul(int x,int y){return 1ll*x*y%mod;}
int qpow(int x,int y)
{
int res=1;
for(;y;y>>=1,x=mul(x,x)) if(y&1) res=mul(res,x);
return res;
}
int C(int x,int y){if(y>x)return 0;return mul(fac[x],mul(ifac[x-y],ifac[y]));}
void initsth()
{
fac[0]=1;bas[0]=1;pw[0]=1;
for(int i=1;i<N;++i)
{
fac[i]=mul(fac[i-1],i);
bas[i]=mul(bas[i-1],10);
pw[i]=mul(pw[i-1],2);
}
ifac[N-1]=qpow(fac[N-1],mod-2);
for(int i=N-2;~i;--i)ifac[i]=mul(ifac[i+1],i+1);
inv[0]=1;for(int i=1;i<N;++i) inv[i]=mul(ifac[i],fac[i-1]);
}
}
using namespace Math;
int n,K;
int pre[N],suf[N];
int sum[N],ss[N];
char s[N];
int main()
{
initsth();
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%s",&K,s+1);n=strlen(s+1);
for(int i=0;i<=n;++i) sum[i]=ss[i]=pre[i]=suf[i]=0;
for(int i=1;i<=n;++i) sum[1]+=s[i]-'0',ss[i]=ss[i-1]+s[i]-'0';
for(int i=1,now=0;i<=n;++i)
{
now=(mul(now,10)+(s[i]-'0'))%mod;
pre[i]=now;
}
for(int i=n,now=0;i>=1;--i)
{
now=upm(now+mul(bas[n-i],s[i]-'0'));
suf[n-i+1]=now;
}
for(int len=2;len<=n;++len)
{
sum[len]=upm(sum[len-1]+mul(bas[len-1],ss[n-len+1]));
up(sum[len],mod-pre[len-1]);
}
for(int i=1;i<n;++i) up(sum[i],mod-pre[i]),up(sum[i],mod-suf[i]);
up(sum[n],pre[n]);
//for(int i=1;i<=n;++i) printf("%d %d\n",i,sum[i]);
int ans=0;
K=min(n,K);
if(K>=3)
{
int now=pw[K-3];
for(int len=n;len>=1;--len)
{
int res=n-len-2;
if(res<0) continue;
if(res<=K-3) up(ans,mul(sum[len],pw[res]));
else
{
now=mul(now,2);up(now,mod-C(res-1,K-3));
up(ans,mul(now,sum[len]));
}
}
now=pw[K-2];
for(int i=n;i>=1;--i)
{
int res=n-i-1;
if(res<0) continue;
if(res<=K-2)
{
up(ans,mul(pre[i],pw[res]));
up(ans,mul(suf[i],pw[res]));
}
else
{
now=mul(now,2);up(now,mod-C(res-1,K-2));
up(ans,mul(pre[i],now));
up(ans,mul(suf[i],now));
}
}
}
else if(K==2)
{
for(int i=2;i<=n;++i)
{
up(ans,pre[i]);up(ans,suf[i]);
}
}
up(ans,pre[n]);
printf("%d\n",ans);
}
return 0;
}
1006. GCD Game
【题意】
一个长度为 n n n的序列 a i a_i ai,两个人博弈,每次可以任选一个 a i a_i ai,再任选一个数字 1 ≤ x < a i 1\leq x<a_i 1≤x<ai,使 a i = gcd ( x , a i ) a_i=\gcd(x,a_i) ai=gcd(x,ai),不能操作的人输。问谁赢。
n ≤ 1 0 6 , a i ≤ 1 0 7 n\leq 10^6,a_i\leq 10^7 n≤106,ai≤107
【思路】
这个东西和 N i m Nim Nim游戏是本质相同的,一个数的所有素数的次数和等价于一堆石头个数。
复杂度 O ( n log n + A ) O(n\log n+A) O(nlogn+A)
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int x;scanf("%d",&x);return x;
}
const int maxn=3e7+555;
int prm[maxn/10],minp[maxn],tot=0;
bool vis[maxn];
int n,a[maxn/10];
void euler(int n){
vis[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]) prm[++tot]=i,minp[i]=i;
for(int j=1;j<=tot&&prm[j]*i<=n;j++){
vis[i*prm[j]]=1;
minp[i*prm[j]]=min(minp[i],prm[j]);
if(i%prm[j]==0) break;
}
}
}
int fen(int x){
int ans=0;while(x>1) ans++,x/=minp[x];
return ans;
}
int main(){
euler(1e7);
dwn(_,yh(),1){
ll ans=0;
n=yh();rep(i,1,n){
int x=yh();
ans^=fen(x);
}
puts(ans?"Alice":"Bob");
}
return 0;
}
1007. A Simple Problem
【题意】
一个长度为 n n n的全0序列 a i a_i ai,一个正数 k k k,有 q q q个操作:
- 区间加某个数
- 求区间所有长度为 k k k的子区间最大值的最小值
k , n ≤ 5 × 1 0 8 , q ≤ 1 0 5 k,n\leq 5\times10^8,q\leq 10^5 k,n≤5×108,q≤105
【思路】
用线段树维护区间加,区间
max
\max
max。
考虑统计在两段之间的答案,前一段的后缀
max
\max
max和后一段的前缀
max
\max
max都是单调的。
可以在序列上二分,每次选择一段正好为
k
k
k的区间,如果前一段的
max
\max
max更大,最优的区间不会在这段区
间的左边,否则不会在这段区间的右边。
直接用这种方法合并答案,即可得到的做法
O
(
q
log
3
n
)
O(q\log^3 n)
O(qlog3n)
如果能在线段树上二分,就可以做到
O
(
q
log
2
n
)
O(q\log^2 n)
O(qlog2n)。
正解:
对于所有的询问,
k
k
k是定值,将序列按
k
k
k分段,超过
n
n
n的部分补至
k
k
k的倍数。
连续的
k
k
k个数要么在一段内,要么在两段之间。
对于每一段建一棵线段树,每一段的线段树形态完全一样,二分可以在线段树上做,单次二分的复杂度
O
(
log
n
)
O(\log n)
O(logn)
为。
每次修改只需要对端点处的
O
(
1
)
O(1)
O(1)段区间重新计算答案。
另外建一棵线段树维护
n
k
\frac n k
kn段的答案,需要支持区间加,单点修改,区间查询
min
\min
min。
对于修改,中间段做区间加,两端重新计算。
对于询问,中间段查询
min
\min
min,两端不足一个段的部分用二分计算答案
复杂度 O ( q log n ) O(q\log n) O(qlogn)
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
const int N = 111111;
const int M = N*60;
const int inf = 1e9+N;
int n,k,q,d,tot,rt,ls[M],rs[M];
int t[M],w[M],s[M],e[M];
void ch(int u,int x){
w[u]+=x;
t[u]+=x;
s[u]+=x;
e[u]+=x;
}
void modify2(int&u,int L,int R,int X,int l,int r){
if(!u)
u=++tot;
if(L<=l&&r<=R){
ch(u,X);
return;
}
int h=l+r>>1;
if(L<=h)
modify2(ls[u],L,R,X,l,h);
if(h<R)
modify2(rs[u],L,R,X,h+1,r);
t[u]=max(t[ls[u]],t[rs[u]])+w[u];
}
int solve(int u,int l,int r,int cl=0,int cr=k-1){
int h=l+r>>1,fl;
int u1=ls[u],l1=l,r1=h,u2=rs[u],l2=h+1,r2=r;
int w1=w[u1],w2=w[u2],s=inf,o;
while(l1!=r1){
u1=rs[u1];
w1+=w[u1];
l1=(l1+r1>>1)+1;
}
while(l2!=r2){
u2=ls[u2];
w2+=w[u2];
r2=l2+r2>>1;
}
l=0,r=k-1;
o=-inf;
while(l!=r){
h=l+r>>1;
if(cl<=h+1&&h+1<=cr)
s=min(s,max(o,max(w1+t[rs[u1]],w2+t[ls[u2]])));
if(cr<h+1)
fl=0;
else{
if(h+1<cl)
fl=1;
else
fl=w1+t[rs[u1]]>=w2+t[ls[u2]];
}
if(fl){
o=max(o,w2+t[ls[u2]]);
l=h+1;
u1=rs[u1];
u2=rs[u2];
}
else{
o=max(o,w1+t[rs[u1]]);
r=h;
u1=ls[u1];
u2=ls[u2];
}
w1+=w[u1];
w2+=w[u2];
}
return s;
}
void modify1(int&u,int L,int R,int X,int l=0,int r=d){
if(!u)
u=++tot;
if(L<=l*k&&r*k+k-1<=R){
ch(u,X);
return;
}
if(l==r){
modify2(u,L,R,X,l*k,r*k+k-1);
s[u]=t[u];
return;
}
int h=l+r>>1;
if(L<=h*k+k-1)
modify1(ls[u],L,R,X,l,h);
if((h+1)*k<=R)
modify1(rs[u],L,R,X,h+1,r);
t[u]=max(t[ls[u]],t[rs[u]])+w[u];
if(L<=h*k&&(h+1)*k+k-1<=R)
e[u]+=X;
else{
if(L<=(h+1)*k+k-1||h*k<=R)
e[u]=solve(u,l,r)+w[u];
}
s[u]=min(min(s[ls[u]],s[rs[u]])+w[u],e[u]);
}
int query(int u,int L,int R,int l=0,int r=d){
if(R<l*k+k-1||r*k<L)
return inf;
if(!u)
return 0;
if(L<=l*k&&r*k+k-1<=R)
return s[u];
if(l==r)
return inf;
int h=l+r>>1,o;
if(R<=h*k+k-1)
return query(ls[u],L,R,l,h)+w[u];
if((h+1)*k<=L)
return query(rs[u],L,R,h+1,r)+w[u];
if(h*k<L||R<(h+1)*k+k-1)
o=solve(u,l,r,max(0,L-h*k),min(k-1,R-(h+1)*k+1))+w[u];
else
o=e[u];
return min(o,min(query(ls[u],L,R,l,h),query(rs[u],L,R,h+1,r))+w[u]);
}
int main()
{
//freopen("input2.txt","r",stdin);
//freopen("std.out","w",stdout);
int T,i,l,r,x;
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&k,&q);
d=(n-1)/k;
n=(d+1)*k;
while(q--){
scanf("%d%d%d",&i,&l,&r);
l--,r--;
if(i==1){
scanf("%d",&x);
modify1(rt,l,r,x);
}
else{
printf("%d\n",query(rt,l,r));
}
}
tot=0,rt=0;
memset(ls,0,sizeof(ls));
memset(rs,0,sizeof(rs));
memset(t,0,sizeof(t));
memset(w,0,sizeof(w));
memset(s,0,sizeof(s));
memset(e,0,sizeof(e));
}
return 0;
}
1008. Squard Card
【题意】
一个平面内有两个圆 A , B A,B A,B,往平面上随机丢一个长度为 a a a的正方形卡片,如果卡片绕卡片中心旋转任意度数后能完全在一个圆内,则得分。
问在 A A A种得分的情况下在 B B B中也得分的概率
多组数据。
【思路】
考虑得分的面积,那么概率就是 交 的 面 积 A 的 面 积 \frac {交的面积}{A的面积} A的面积交的面积,然后这个得分的面积事实上就是一个圆,大概是正方形的一条边作为圆的弦为边界情况,那么可行的范围半径是 r 2 − a 2 4 − a 2 \sqrt{r^2-\frac {a^2} 4}-\frac a 2 r2−4a2−2a。
求圆交就行。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
int t;
int r1,r2,xa,ya,xb,yb,a;
const double pi=acos(-1.0);
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d%d%d%d%d",&r1,&xa,&ya,&r2,&xb,&yb,&a);
if(sqrt(2.0)*r2<a)
{
puts("0.000000");
continue;
}
double ra=sqrt(r1*r1-a*a/4.0)-a/2.0,rb=sqrt(r2*r2-a*a/4.0)-a/2.0;
double d=sqrt((double)(xa-xb)*(xa-xb)+(ya-yb)*(ya-yb));
if(ra+rb<=d)
{
puts("0.000000");
continue;
}
if(ra>=rb+d)
{
printf("%.6f\n",rb*rb/ra/ra);
continue;
}
if(rb>=ra+d)
{
puts("1.000000");
continue;
}
double ca=(ra*ra+d*d-rb*rb)/ra/d/2.0,cb=(rb*rb+d*d-ra*ra)/rb/d/2.0;
double s=acos(ca)*ra*ra+acos(cb)*rb*rb-ra*ra*ca*sqrt(1.0-ca*ca)-rb*rb*cb*sqrt(1.0-cb*cb);
printf("%.6f\n",s/pi/ra/ra);
}
return 0;
}
1009. Singing Superstar
【题意】
给定一个串 S S S,再给 q q q个询问串 t i t_i ti,问 t i t_i ti在 S S S中不重重叠地出现了多少次。
∣ S ∣ , q ≤ 1 0 5 , ∣ t i ∣ ≤ 30 |S|,q\leq 10^5,|t_i|\leq 30 ∣S∣,q≤105,∣ti∣≤30
【思路】
这里有一个关键是 t i t_i ti很小,所以一个做法是将 S S S所有长度不超过30的串都用哈希求出答案即可。
复杂度 O ( 30 ⋅ ∣ S ∣ + ∑ ∣ t i ∣ ) O(30\cdot |S|+\sum |t_i|) O(30⋅∣S∣+∑∣ti∣),可能有个map的log
当然一个更“准确”的做法是,所有询问串建AC自动机,然后用 S S S在自动机上匹配,每次跑到一个节点更新这个节点代表的字符串的答案即可。
复杂度 O ( 串 长 和 ) O(串长和) O(串长和)
【参考代码】
hash
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
const int bas1=29,bas2=33,mod1=1e9+9,mod2=1e9+7;
const int N=1e5+10;
int sum1[N],sum2[N],bas11[N],bas22[N];
char s[N];
map<pii,int>mp1,mp2;
pii gethash1(int l,int r)
{
int s1=0,s2=0;
s1=((sum1[r]-1ll*sum1[l-1]*bas11[r-l+1]%mod1)+mod1)%mod1;
s2=((sum2[r]-1ll*sum2[l-1]*bas22[r-l+1]%mod2)+mod2)%mod2;
//printf("%d %d %d %d\n",l,r,s1)
return mkp(s1,s2);
}
pii gethash(char *t,int l,int r)
{
int s1=0,s2=0;
for(int i=l;i<=r;++i)
{
s1=(1ll*s1*bas1+t[i]-'a'+1)%mod1;
s2=(1ll*s2*bas2+t[i]-'a'+1)%mod2;
}
return mkp(s1,s2);
}
int main()
{
//freopen("data.in","r",stdin);
//freopen("my.out","w",stdout);
int T;scanf("%d",&T);
bas11[0]=bas22[0]=1;
for(int i=1;i<N;++i)
{
bas11[i]=1ll*bas11[i-1]*bas1%mod1;
bas22[i]=1ll*bas22[i-1]*bas2%mod2;
}
while(T--)
{
int n,m;
scanf("%s",s+1);n=strlen(s+1);
mp1.clear();mp2.clear();
for(int i=1;i<=n;++i)
{
sum1[i]=(1ll*sum1[i-1]*bas1+s[i]-'a'+1)%mod1;
sum2[i]=(1ll*sum2[i-1]*bas2+s[i]-'a'+1)%mod2;
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=30;++j)
{
if(i-j+1<1) break;
pii t=gethash1(i-j+1,i);
//printf("%d %d %d %d\n",i-j+1,i,t.fi,t.se);
if(mp1[t]>=i-j+1) continue;
else mp1[t]=i,mp2[t]++;
}
}
scanf("%d",&m);
while(m--)
{
scanf("%s",s+1);n=strlen(s+1);
pii t=gethash(s,1,n);
if(!mp2.count(t)) puts("0");
else printf("%d\n",mp2[t]);
}
}
return 0;
}
AC自动机
#include<bits/stdc++.h>
using namespace std;
const int maxn = 6e5 + 5;
const int maxm = 1e5 + 4;
char tmp[35];
int n, ans[maxm], last[maxm], flag[maxm], pos[maxm];
char s[maxm];
namespace AC {
int tr[maxn][27], tot;
int fail[maxn], cnt[maxn], dep[maxn];
void init () {
tot = 0;
for (int i = 0 ; i < maxn ; i++) {
for (int j = 0 ; j < 26 ; j++) tr[i][j] = 0;
fail[i] = cnt[i] = dep[i] = 0;
}
}
int add (char *s) {
int u = 0;
for (int i = 1 ; s[i] ; i++) {
if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
u = tr[u][s[i] - 'a'];
dep[u] = i;
}
return u;
}
queue<int>q;
void build () {
while (q.size()) q.pop();
for (int i = 0 ; i < 26 ; i++) if (tr[0][i]) q.push(tr[0][i]);
while (q.size()) {
int u = q.front(); q.pop();
for (int i = 0 ; i < 26 ; i++) {
if (tr[u][i]) {
fail[tr[u][i]] = tr[fail[u]][i];
q.push(tr[u][i]);
} else {
tr[u][i] = tr[fail[u]][i];
}
}
}
}
void ask (char *t) {
int u = 0;
memset(last, -1, sizeof last);
for (int i = 1 ; t[i] ; i++) {
u = tr[u][t[i] - 'a']; // 转移
for (int j = u ; j ; j = fail[j]) {
if (last[j] == -1 || i - last[j] >= dep[j]) {
cnt[j]++;
last[j] = i;
}
}
}
return ;
}
}
int main() {
int T = 0, m;
scanf("%d", &T);
while (T--) {
scanf("%s%d", s + 1, &m);
memset(ans, 0, sizeof ans);
AC::init();
for (int i = 1 ; i <= m; i++) {
scanf("%s", tmp + 1);
pos[i] = AC::add(tmp);
}
AC::build();
AC::ask(s);
for (int i = 1 ; i <= m ; i++) printf("%d\n", AC::cnt[pos[i]]);
}
return 0;
}
1010. Yinyang
【题意】
一个 n × m n\times m n×m的网格,要给它染黑白,有些位置已经染了。要求最后所有黑色联通,白色也联通,且任意 2 × 2 2\times 2 2×2矩阵四个颜色非同色。
n × m ≤ 100 n\times m\leq 100 n×m≤100
【思路】
所有黑色和白色分别要连通,那么构成环的只可能是网格最外面一圈。
下面不妨设 n ≥ m n\geq m n≥m,考虑DP
DP时需要记录前 m m m个格子的颜色和连通性,这样的状态不超过20000个,转移时枚举下一位置的颜色,不允许形成环,特判最后两个格子即可。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
const int N = 55;
const int M = 15;
const int MO = 998244353;
void ad(int&x,int y){
x+=y;
if(x>=MO)
x-=MO;
}
int mul(int x,int y){
return (LL)x*y%MO;
}
int n,m,a[N][N];
int b[M],f[M];
LL geth(){
int i;
LL h=0;
for(i=1;i<=m;i++)
h=h*2+b[i];
for(i=1;i<=m;i++)
h=h*16+f[i];
if(f[f[f[f[m]-1]-1]-1]>1)
return -1;
return h;
}
void getbf(LL h){
int i;
for(i=m;i>=1;i--)
f[i]=h&15,h>>=4;
for(i=m;i>=1;i--)
b[i]=h&1,h>>=1;
}
map<LL,int> mp;
vector<pair<LL,int> > v[2];
void dfs(int k){
if(k==m+1){
LL h=geth();
if(h>=0)
v[0].push_back(make_pair(h,1));
return;
}
int i;
for(i=0;i<2;i++){
if(a[1][k]>=0&&a[1][k]!=i)
continue;
b[k]=i;
if(k==1||b[k]!=b[k-1])
f[k]=k;
else
f[k]=f[k-1];
dfs(k+1);
}
}
LL nx(LL h,int t,int e){
int i,x,y;
getbf(h);
if(b[t]==e){
if(t==1)
return h;
if(f[t]==f[t-1])
return -1;
if(b[t]==b[t-1]){
x=min(f[t],f[t-1]);
y=max(f[t],f[t-1]);
for(i=1;i<=m;i++)
if(f[i]==y)
f[i]=x;
}
return geth();
}
x=0;
for(i=1;i<=m;i++)
if(i!=t&&f[i]==f[t]){
x=i;
break;
}
if(!x)
return -1;
y=f[t];
for(i=1;i<=m;i++)
if(f[i]==y)
f[i]=x;
b[t]=e;
f[t]=t;
if(t!=1&&b[t]==b[t-1])
f[t]=f[t-1];
return geth();
}
void go(int i,int a){
int j;
LL x,y;
for(j=0;j<v[0].size();j++){
x=v[0][j].first;
if(a!=1){
y=nx(x,i,0);
if(y>=0)
v[1].push_back(make_pair(y,v[0][j].second));
}
if(a!=0){
y=nx(x,i,1);
if(y>=0)
v[1].push_back(make_pair(y,v[0][j].second));
}
}
v[0].clear();
sort(v[1].begin(),v[1].end());
x=-1;
j=-1;
for(i=0;i<v[1].size();i++){
if(v[1][i].first==x)
ad(v[0][j].second,v[1][i].second);
else{
v[0].push_back(v[1][i]);
x=v[1][i].first;
j++;
}
}
v[1].clear();
}
int cal(LL h,int a1,int a2){
int i,o=0;
getbf(h);
if(b[m-2]==b[m-1]&&b[m-1]==b[m])
return 0;
if(b[m-1]==b[m]){
for(i=1;i<m-2;i++){
if(b[i]==b[m]){
if(f[i]!=f[m])
return 0;
}
else{
if(f[i]!=f[m-2])
return 0;
}
}
if(a1!=b[m]&&a2!=b[m])
o++;
if(a1!=b[m]&&a2!=b[m-2])
o++;
return o;
}
if(b[m-2]==b[m-1]){
if(f[m-2]==f[m-1]){
for(i=1;i<m-2;i++){
if(b[i]==b[m]){
if(f[i]!=f[m])
return 0;
}
else{
if(f[i]!=f[m-2])
return 0;
}
}
if(a1!=b[m-2]&&a2!=b[m-2])
o++;
return o;
}
for(i=1;i<m-2;i++){
if(b[i]==b[m]){
if(f[i]!=f[m])
return 0;
}
else{
if(f[i]!=f[m-2]&&f[i]!=f[m-1])
return 0;
}
}
if(a1!=b[m]&&a2!=b[m])
o++;
if(a1!=b[m]&&a2!=b[m-2])
o++;
return o;
}
if(f[m-2]==f[m]){
for(i=1;i<m-2;i++){
if(b[i]==b[m]){
if(f[i]!=f[m])
return 0;
}
else{
if(f[i]!=f[m-1])
return 0;
}
}
if(a1!=b[m-1]&&a2!=b[m-1])
o++;
if(a1!=b[m]&&a2!=b[m-1])
o++;
if(a1!=b[m]&&a2!=b[m])
o++;
return o;
}
for(i=1;i<m-2;i++){
if(b[i]==b[m]){
if(f[i]!=f[m]&&f[i]!=f[m-2])
return 0;
}
else{
if(f[i]!=f[m-1])
return 0;
}
}
if(a1!=b[m-1]&&a2!=b[m-1])
o++;
return o;
}
int main()
{
//freopen("input.txt","r",stdin);
//freopen("std2.out","w",stdout);
int T,i,j,s;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
scanf("%d",a[i]+j);
}
}
if(n<m){
for(i=1;i<=m;i++)
for(j=i+1;j<=m;j++)
swap(a[i][j],a[j][i]);
swap(n,m);
}
v[0].clear();
dfs(1);
for(i=2;i<n;i++){
for(j=1;j<=m;j++){
go(j,a[i][j]);
}
}
for(j=1;j<=m-2;j++)
go(j,a[n][j]);
s=0;
for(i=0;i<v[0].size();i++)
ad(s,mul(cal(v[0][i].first,a[n][m-1],a[n][m]),v[0][i].second));
printf("%d\n",s);
}
return 0;
}