【前言】
HDU第一场体验良好,除了1007罚时属实多了,1002的KD树没写,其他做起来还行,没有痛苦面具。
最后rk42,校2/9
1001. Mod, Or and Everything
【题意】
给定一个正整数
n
n
n,求
(
n
mod
1
)
or
(
n
mod
2
)
or
⋯
or
(
n
mod
n
)
(n\text{ mod }1)\text{ or }(n\text{ mod }2)\text{ or } \cdots \text{ or }(n\text{ mod }n)
(n mod 1) or (n mod 2) or ⋯ or (n mod n)
n
≤
1
0
12
n\leq 10^{12}
n≤1012,
T
≤
5000
T\leq 5000
T≤5000
【思路】
这种题一看就很有规律,打表找一下规律就行,大概会发现答案是 2 k − 1 2^k-1 2k−1这种
复杂度 O ( T log n ) O(T\log n) O(Tlogn)
【参考代码】
#include<bits/stdc++.h>
#define int long long
#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 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=3e5+5;
signed main(){
vector<pii> val;
int now=2,cur=2,sum=0;
val.pb({2,0});
rep(i,1,62){
cur+=now;
sum=(1ll<<i)-1;
val.pb({cur,sum});
now<<=1;
// cout<<cur<<" "<<sum<<hvie;
}
dwn(_,yh(),1){
int n=yh();
auto it=lower_bound(val.begin(),val.end(),(pii){n,0ll});
// it--;
cout<<it->se<<hvie;
}
return 0;
}
1002. Rocket land
【题意】
x O y xOy xOy平面上有 n n n个带权的火箭按编号顺序发射,问第 i i i个火箭半径为 r i r_i ri的圆范围内所有已经发射的火箭权值和。
n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n≤2×105,数据随机
【思路】
看到这个数据随机,又是平面权值和问题,就直接上KD树就行。
离线建树可以不用重建,主要需要注意判定矩形是否被完全覆盖or相离。
复杂度 O ( n n ) O(n\sqrt n) O(nn)
【参考代码】
#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;
const int N=5e5+10,K=2,mod=1e9+7,inf=0x3f3f3f3f;
int n,r[N],pos[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;
}
int now=0;
struct Point
{
int d[K],val,id;
friend bool operator < (const Point&A,const Point&B)
{
return A.d[now]<B.d[now];
}
}p[N],a[N];
void gmin(int &x,int y){x=min(x,y);}
void gmax(int &x,int y){x=max(x,y);}
ll sqr(ll x){return x*x;}
struct KDT
{
#define ls t[x].lc
#define rs t[x].rc
int sz,rt;
struct node
{
ll sum;
int mi[K],mx[K],lc,rc,exist,siz,fa;
Point p;
}t[N<<2];
void up(int x)
{
for(int i=0;i<K;++i)
{
t[x].mi[i]=t[x].mx[i]=t[x].p.d[i];
if(ls)
{
gmin(t[x].mi[i],t[ls].mi[i]);
gmax(t[x].mx[i],t[ls].mx[i]);
}
if(rs)
{
gmin(t[x].mi[i],t[rs].mi[i]);
gmax(t[x].mx[i],t[rs].mx[i]);
}
}
if(ls) t[ls].fa=x;
if(rs) t[rs].fa=x;
}
void pushup(int x)
{
t[x].sum=t[x].p.val*t[x].exist;
t[x].siz=t[x].exist;
if(ls) t[x].sum+=t[ls].sum,t[x].siz+=t[ls].siz;
if(rs) t[x].sum+=t[rs].sum,t[x].siz+=t[rs].siz;
}
void clean_point(int x)
{
t[x].sum=t[x].exist=0;t[x].siz=0;
ls=rs=0;t[x].fa=0;
}
void build(int &x,int l,int r,int D)
{
if(l>r)
{
x=0;
return;
}
x=++sz;clean_point(x);now=D;
int mid=(l+r)>>1;
nth_element(a+l,a+mid,a+r+1);
t[x].p=a[mid];pos[a[mid].id]=x;
build(ls,l,mid-1,D^1);
build(rs,mid+1,r,D^1);
up(x);
}
bool inside(int xc,int yc,int r,int xa,int ya,int xb,int yb)//left down && right up
{
ll tmp=sqr(r);
return (sqr(xa-xc)+sqr(ya-yc)<=tmp) && (sqr(xa-xc)+sqr(yb-yc)<=tmp) &&
(sqr(xb-xc)+sqr(ya-yc)<=tmp) && (sqr(xb-xc)+sqr(yb-yc)<=tmp);
}
bool outside(int xc,int yc,int r,int xa,int ya,int xb,int yb)
{
int tx,ty;
if(xa<=xc && xc<=xb) tx=xc;
else
{
if(xa>=xc) tx=xa;
else tx=xb;
}
if(ya<=yc && yc<=yb) ty=yc;
else
{
if(ya>=yc) ty=ya;
else ty=yb;
}
return (sqr(tx-xc)+sqr(ty-yc)>sqr(r));
}
ll query(int x,int xc,int yc,int r)
{
if(!x || !t[x].siz) return 0;
if(inside(xc,yc,r,t[x].mi[0],t[x].mi[1],t[x].mx[0],t[x].mx[1])) return t[x].sum;
if(outside(xc,yc,r,t[x].mi[0],t[x].mi[1],t[x].mx[0],t[x].mx[1])) return 0;
return query(ls,xc,yc,r)+query(rs,xc,yc,r)+
inside(xc,yc,r,t[x].p.d[0],t[x].p.d[1],t[x].p.d[0],t[x].p.d[1])*t[x].p.val*t[x].exist;
}
void update(int x)
{
x=pos[x];t[x].exist=1;
while(x) pushup(x),x=t[x].fa;
}
void clear()
{
sz=rt=0;
}
#undef ls
#undef rs
}T;
int main()
{
//freopen("1.in","r",stdin);
//freopen("a.out","w",stdout);
for(int TT=read();TT--;)
{
n=read();T.clear();
for(int i=1;i<=n;++i) p[i].d[0]=read(),p[i].d[1]=read(),p[i].val=read(),p[i].id=i,r[i]=read(),a[i]=p[i];
T.build(T.rt,1,n,0);
for(int i=1;i<=n;++i)
{
T.update(i);
ll ans=T.query(T.rt,p[i].d[0],p[i].d[1],r[i]);
printf("%lld\n",ans);
}
}
}
1003. Puzzle loop
【题意】
n + 1 n+1 n+1条水平线和 m + 1 m+1 m+1条竖直线构成了一个 n × m n\times m n×m的网格,现在要把这个网格的某些边涂色,使得满足构成某个格子的四条边被涂色的次数为奇数或偶数(或无限制),同时要求涂色的边构成若干个闭合图形且每个闭合图形内部没有涂色边。问可行的方案数
n , m ≤ 17 n,m\leq 17 n,m≤17
【思路】
首先一个比较重要的观察是,闭合图形内部没有边,对于一个闭合图形,我们可以等价地看成是这个图形的每一个小方格都把四条边涂上色(可以重复),这样并不会改变奇偶性。
那么问题等价于把每一个小方格独立考虑是不是把四条边都涂色,看上去很轮廓线DP,然后比赛的时候队友写了一半发现做不动就没这么搞了。
不过想了一会我就给出了比较正确的解法。(榜好像歪了,过这个题的人不多)
事实上,每个方格边的涂色奇偶性之和它相邻的四个个涂不涂有关,这就是一个异或方程组,我们要求的就是异或方程组解的个数,就是求自由元个数 k k k,答案就是 2 k 2^k 2k ,当然要判无解。
复杂度 O ( n 6 ) O(n^6) O(n6),但是解异或方程跑的飞快。甚至还可以bitset一下。
【参考代码】
#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 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=3e5+5;
int A[350][350];
int m,n;
int xor_guass(int m, int n) //A是异或方程组系数矩阵 返回秩
{
int i = 0, j = 0, k, r, u;
while(i < m && j < n){//当前正在处理第i个方程,第j个变量
r = i;
for(int k = i; k < m; k++) if(A[k][j]){r = k; break;}
if(A[r][j]){
if(r != i) for(k = 0; k <= n; k++) swap(A[r][k], A[i][k]);
//消元完成之后第i行的第一个非0列是第j列,且第u>i行的第j列全是0
for(u = i + 1; u < m; u++) if(A[u][j])
for(k = i; k <= n; k++) A[u][k] ^= A[i][k];
i++;
}
j++;
}
// rep(i,0,m-1){
// rep(j,0,n-1)
// }
dwn(i,m-1,0){
bool all0=1;
rep(j,0,n-1) if(A[i][j]) {
all0=0;break;
}
if(all0&&A[i][n]){
return 0x3f3f3f3f;
}
}
// cout<<i<<hvie;
return i;
}
int id[18][18],dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
ll ksm(ll x,ll p,ll mod){
ll ans=1;
for(;p;p>>=1,x=x*x%mod) if(p&1) ans=ans*x%mod;
return ans;
}
int main(){
dwn(_,yh(),1){
m=yh()-1,n=yh()-1;
rep(i,0,m-1)rep(j,0,n-1) id[i][j]=i*n+j;
int row=0;
// memset(A,0,sizeof(A));
rep(i,0,m-1)rep(j,0,n-1){
char c=getchar();while(!isdigit(c)&&c!='.')c=getchar();
rep(k,0,m*n) A[row][k]=0;
if(isdigit(c)){
rep(k,0,3){
int i_=i+dx[k];
int j_=j+dy[k];
if(i_>=0&&j_>=0&&i_<m&&j_<n){
A[row][id[i_][j_]]=1;
}
}
// A[row][id[i][j]]=1;
A[row][m*n]=(int)(c-'0');
row++;
}
}
int rk=xor_guass(row,m*n);
if(rk==0x3f3f3f3f){
puts("0");
}
else cout<<ksm(2,(n*m-rk),998244353)<<hvie;
}
return 0;
}
1004. Another thief in a Shop
【题意】
商店里有 n n n种物品,第 i i i种的价值是 a i a_i ai,每种可以无限取,问有多少种取的方式使得总价值能到 k k k,答案对一个常见素数取模。
n ≤ 100 , a i ≤ 10 , k ≤ 1 0 18 n\leq 100,a_i\leq 10,k\leq 10^{18} n≤100,ai≤10,k≤1018
【思路】
首先一个Naive的想法是,每种取的方案实际上是一个生成函数的形式,即 1 1 − x a i \frac 1 {1-x^{a_i}} 1−xai1,答案就是要求这 n n n个生成函数的乘积的第 k k k项系数。
但这样似乎做不了。
一般来说,这种东西肯定是插值插出来的,但是比赛想了很久也没有想到,数学功底不太行。
初始设
f
[
0
]
=
1
f[0]=1
f[0]=1,对于每个
a
[
i
]
a[i]
a[i],有如下转移式
f
[
j
]
=
f
[
j
−
a
[
i
]
]
f[j]=f[j-a[i]]
f[j]=f[j−a[i]]
这样我们就得出了一个
O
(
n
k
)
O(nk)
O(nk)的DP
如果转换为数学形式,令
f
(
i
,
j
)
f(i,j)
f(i,j)表示只拿前
j
j
j个物品时的函数值,那么就有:
f
(
i
,
j
)
=
∑
i
=
0
⌊
i
a
j
⌋
f
(
i
−
a
j
,
j
−
1
)
f(i,j)=\sum_{i=0}^{\lfloor \frac{i}{a_j} \rfloor}f(i-a_j,j-1)
f(i,j)=i=0∑⌊aji⌋f(i−aj,j−1)
最后我们会发现,答案
f
(
k
,
n
)
f(k,n)
f(k,n)的所有
∑
\sum
∑ 符号上面的值均是
k
a
j
\frac {k}{a_j}
ajk,于是如果我们对
k
k
k模
a
[
i
]
a[i]
a[i]的余数进行分类讨论,就能得到一个不带整除符号的求和式子。
由于
a
[
i
]
≤
10
a[i]\leq 10
a[i]≤10,我们只需要考虑
k
k
k模
lcm
(
a
[
i
]
)
\text{lcm}(a[i])
lcm(a[i])(lcm不超过2520)的余数,答案就会变成一个只与
k
lcm
\frac {k} {\text{lcm}}
lcmk有关的
n
n
n次整系数多项式,然后就可以插值了。
复杂度 O ( n 2 ⋅ lcm ) O(n^2\cdot \text{lcm}) O(n2⋅lcm)
#include<bits/stdc++.h>
using namespace std;
#define N 6000010
#define LL long long
#define MOD 1000000007
int T,n,a[110],f[N];
LL k,y[110],pre[110],suf[110],inv[110];
LL Lagrange(LL x)
{
LL res=0;
pre[0]=suf[n+1]=1;
for(int i=1;i<=n;i++)
pre[i]=1ll*pre[i-1]*((x-i)%MOD)%MOD;
for(int i=n;i>=1;i--)
suf[i]=1ll*suf[i+1]*((x-i)%MOD)%MOD;
for(int i=1;i<=n;i++)
{
int l=1ll*pre[i-1]*suf[i+1]%MOD*inv[n-i]%MOD*inv[i-1]%MOD;
l=1ll*l*y[i]%MOD;
if((n^i)&1)l=MOD-l;
res=(res+l)%MOD;
}
return res;
}
int main()
{
inv[0]=inv[1]=f[0]=1;
for(LL i=2;i<110;i++)inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
for(LL i=2;i<110;i++)inv[i]=inv[i]*inv[i-1]%MOD;
scanf("%d",&T);
while(T--){
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int lcm=a[1];
for(int i=2;i<=n;i++){
int d=__gcd(lcm,a[i]);
lcm=lcm*a[i]/d;
}
for(int i=1;i<=n*lcm;i++)f[i]=0;
for(int j=1;j<=n;j++){
for(int i=a[j];i<=n*lcm;i++){
f[i]=(f[i]+f[i-a[j]])%MOD;
}
}
if(k<=n*lcm){
printf("%d\n",f[k]);
continue;
}
for(int i=k%lcm;i<n*lcm;i+=lcm)y[i/lcm+1]=f[i];
printf("%lld\n",Lagrange(k/lcm+1));
}
}
1005. Minimum Spanning Tree
【题意】
n − 1 n-1 n−1个点编号 2 2 2到 n n n,两点之间的边权为它们编号的 lcm \text{lcm} lcm,求最小生成树权值。
n ≤ 1 0 7 n\leq 10^7 n≤107,多组数据
【思路】
考虑最小生成树的构建,我们只需要每个编号往前找一个最小的连就行了(往后一定不优)。
显然素数一定和 2 2 2相连,否则一定会让权值变大
非素数往它的一个因数连就行,答案是它本身。
两部分答案分开统计一下就行。
复杂度 O ( n + T ) O(n+T) O(n+T)
【参考代码】
#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;
const int N=1e7+1,M=22,mod=1e9+7,inf=0x3f3f3f3f;
bool notpri[N];
int pri[1000000],cnt;
ll sum[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;
}
void init()
{
notpri[1]=1;
for(int i=2;i<N;++i)
{
if(!notpri[i]) pri[++cnt]=i;
for(int j=1;j<=cnt;++j)
{
ll t=1ll*i*pri[j];
if(t>=N) break;
notpri[t]=1;
if(!(i%pri[j])) break;
}
}
for(int i=2;i<N;++i) sum[i]=sum[i-1]+(notpri[i]^1)*i;
}
signed main()
{
init();
for(int T=read();T--;)
{
ll n=read();
ll tmp=1ll*n*(n+1)/2-1-sum[n];
ll ans=max(0ll,(sum[n]-2))*2+tmp;
printf("%lld\n",ans);
}
return 0;
}
1006. Xor Sum
【题意】
给定一个长度为 n n n的序列 a i a_i ai,找到一个最短的连续序列,使得异或和大于等于 k k k
n ≤ 1 0 5 , k , a i < 2 30 n\leq 10^5,k,a_i<2^{30} n≤105,k,ai<230
【思路】
经典异或和题。
首先做一个前缀异或和,问题转化为找到距离最近的两点使得两点异或和大于等于 k k k。
Trie上每个点实际上也是一个值域区间,满足异或和大于等于 k k k的点可以表示为连续 log \log log个区间,在Trie上也是 log \log log个点。那么这个东西我们只需要在Trie树每个点上记录能到这个点最靠右的点是哪一个,然后枚举序列的右端点,在Trie树上跑一下所有 ≥ k \geq k ≥k的区间,并且插入右端点即可。
复杂度 O ( n log a i ) O(n\log a_i) O(nlogai)
比赛的时候比较蠢,套了一个二分最右位置,把问题变成了是否存在一个点使得异或和大于等于 k k k,强行把复杂度变成 O ( n log n log a i ) O(n\log n\log a_i) O(nlognlogai),但也能过。
【参考代码】
#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;
const int N=1e5+5,mod=1e9+7,inf=0x3f3f3f3f;
int n,K;
int a[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 Trie
{
const int M=30;
int id[N*30][2],mx[N*30][2],ed[N*30],cnt,rt;
void clear()
{
for(int i=0;i<=cnt;++i) id[i][0]=id[i][1]=0,mx[i][0]=mx[i][1]=-1,ed[i]=0;
//memset(id,0,sizeof(id));
//memset(mx,-1,sizeof(mx));
cnt=rt=1;
}
void insert(int x,int d)
{
int now=rt;
for(int i=M-1;i>=0;--i)
{
int t=(x>>i)&1;
if(!id[now][t]) id[now][t]=++cnt;
mx[now][t]=max(mx[now][t],d);now=id[now][t];
//printf("in [%d][%d]\n",i,t);
}
ed[now]=max(ed[now],d);
//printf("insert:%d %d %d %d\n",x,d,now,ed[now]);
}
int find(int x,int k)
{
//printf("nowfind:%d %d\n",x,k);
int now=rt;
for(int i=M-1;i>=0;--i)
{
int t=(x>>i)&1;
// printf("mine:%d checkmx:%d\n",t,mx[now][t^1]);
if(!id[now][t^1] || mx[now][t^1]<k)
{
//if(!id[i][t]) return -1;
//printf("go:%d %d\n",i,t);
now=id[now][t];
}
else
{
// printf("go:%d %d\n",i,t^1);
now=id[now][t^1];
}
}
if(ed[now]>=k)
{
// printf("get:%d %d\n",now,ed[now]);
return ed[now];
}
else return -1;
}
}tr;
signed main()
{
for(int T=read();T--;)
{
n=read();K=read();
for(int i=1;i<=n;++i) a[i]=read()^a[i-1];
//puts("");
if(K==0)
{
puts("1 1");
continue;
}
int fg=0;
for(int i=1;i<=n;++i)
if((a[i]^a[i-1])>=K)
{
printf("%d %d\n",i,i);
fg=1;
break;
}
if(fg) continue;
tr.clear();
tr.insert(0,0);
int ansl=-1,ansr=n+1;
for(int i=1;i<=n;++i)
{
tr.insert(a[i],i);
int l=0,r=i-1,ret=-1;
while(l<=r)
{
int mid=(l+r)>>1;
//printf("nowfind:%d %d %d\n",mid,i,a[i]);
int gt=tr.find(a[i],mid);
//printf("find:%d %d %d\n",gt,mid,i);
if(gt==-1 || (a[i]^a[gt])<K) r=mid-1;
else l=mid+1,ret=mid;
}
if(ret!=-1)
{
if(i-ret<ansr-ansl+1)
ansl=ret+1,ansr=i;
}
// tr.insert(a[i],i);
//puts("");
}
if(ansr>n) puts("-1");
else printf("%d %d\n",ansl,ansr);
}
return 0;
}
/*
2
3 2
1 2 2
9 7
3 1 3 2 4 0 3 5 1
*/
1007. Pass!
【题意】
有 n n n个人在传球,初始在 1 1 1,已知传若干次球以后回 1 1 1的方案数模998244353的值为 x x x,求满足条件的最少传球次数。
n ≤ 1 0 6 , x ≤ 992344353 n\leq 10^6,x\leq 992344353 n≤106,x≤992344353,多组数据
【思路】
首先我们可以写出一个显然的递推式:令
f
[
i
]
[
0
/
1
]
f[i][0/1]
f[i][0/1]表示传
i
i
i次以后球在不在1的方案数,那么有:
f
[
i
]
[
0
]
=
f
[
i
−
1
]
[
0
]
⋅
(
n
−
2
)
+
f
[
i
−
1
]
[
1
]
f
[
i
]
[
1
]
=
f
[
i
−
1
]
[
0
]
⋅
(
n
−
2
)
f[i][0]=f[i-1][0]\cdot (n-2)+f[i-1][1]\\ f[i][1]=f[i-1][0]\cdot(n-2)
f[i][0]=f[i−1][0]⋅(n−2)+f[i−1][1]f[i][1]=f[i−1][0]⋅(n−2)
然后发现这两个可以合并,设
f
[
i
]
f[i]
f[i]为传
i
i
i次在1的方案,整理一下可以得到:
f
[
i
]
=
(
n
−
2
)
⋅
f
[
i
−
1
]
+
(
n
−
1
)
⋅
f
[
i
−
2
]
f[i]=(n-2)\cdot f[i-1]+(n-1)\cdot f[i-2]
f[i]=(n−2)⋅f[i−1]+(n−1)⋅f[i−2]
n
n
n是一个常数,那么这是一个线性递推的形式,我们可以用特征根方程求出它的通项公式:
f
[
t
]
=
1
n
⋅
(
(
n
−
1
)
t
+
(
n
−
1
)
⋅
(
−
1
)
t
)
f[t]=\frac 1 n\cdot((n-1)^t+(n-1)\cdot(-1)^t)
f[t]=n1⋅((n−1)t+(n−1)⋅(−1)t)
奇偶项分开考虑,那么实际上问题就转化为了
a
b
≡
x
(
mod
998244353
)
a^b\equiv x(\text{mod } 998244353)
ab≡x(mod 998244353)的最小
b
b
b,这个用BSGS就可以了。
复杂度 O ( p ) O(\sqrt{p}) O(p)
【参考代码】
#include <bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#define int long long
const int p = 998244353;
int ksm(int a, int b, int p) {
int s = 1;
for (; b; b >>= 1, a = 1ll * a * a % p) if (b & 1) s = 1ll * s * a % p;
return s;
}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int BSGS(int a, int b, int p) {
if (b == 1) return 0;
int cnt = 0, t = 1;
for (int g = gcd(a, p); g != 1; g = gcd(a, p)) {
if (b % p) return -1;
b /= g, p /= g, t = 1ll * t * (a / g) % p, cnt++;
if (b == t) return cnt;
}
static gp_hash_table<int, int> has; has.clear();
int m = (int)(sqrt(p) + 1), base = b;
for (int i = 0; i < m; i++) has[base] = i, base = 1ll * base * a % p;
base = ksm(a, m, p);
int now = t;
for (int i = 1; i <= m; i++) {
now = 1ll * now * base % p;
if (has[now]) return i * m - has[now] + cnt;
}
return -1;
}
signed main() {
int a, b, T;
scanf("%lld", &T);
while (T--) {
scanf("%lld%lld", &a, &b);
--a;
int tb = b;
b = (p + (a + 1) * tb + a) % p;
int real_ans = p;
int ans = BSGS(a, b, p);
if (ans != -1 && ans % 2 == 1) {
real_ans = ans;
}
b = ((a + 1) * tb - a) % p;
ans = BSGS(a, b, p);
if (ans != -1 && ans % 2 == 0) {
real_ans = min(real_ans, ans);
}
if (real_ans == p) real_ans = -1;
printf("%lld\n", real_ans);
}
return 0;
}
1008. Maximal Submatrix
【题意】
给定一个 n × m n\times m n×m的矩阵,求一个最大的矩形,满足每一列都单调不降。
n , m ≤ 2000 n,m\leq 2000 n,m≤2000
【思路】
单调栈经典题。
求出每一个位置往下的最长连续不下降序列有多长,然后枚举这个位置作为矩形中最“窄”的,左右各做单调栈即可。
复杂度 O ( n m ) O(nm) O(nm)
【参考代码】
/*
* @date:2021-07-20 12:13:04
* @source:
*/
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {
return y > x ? x = y, 1 : 0;
}
template<class T, class G> bool chkMin(T &x, G y) {
return y < x ? x = y, 1 : 0;
}
const int MAXN = 2e3 + 5;
int T, N, M;
vector<int> A[MAXN], H[MAXN];
void input() {
scanf("%d%d", &N, &M);
for (int i = 0; i < N; ++i) {
A[i].resize(M);
for (int j = 0; j < M; ++j) {
scanf("%d", &A[i][j]);
}
}
for (int i = N - 1; i >= 0; --i) {
H[i].resize(M);
for (int j = 0; j < M; ++j) {
if (i == N - 1 || A[i + 1][j] < A[i][j]) H[i][j] = 1;
else H[i][j] = H[i + 1][j] + 1;
}
}
}
int l[MAXN], r[MAXN];
stack<int> area;
stack<int> stk;
int solve(vector<int> &height) {
while (!area.empty()) area.pop();
while (!stk.empty()) stk.pop();
int n = height.size();
for (int i = 0; i < n; i++) {
while (!area.empty() && height[i] < area.top()) {
r[stk.top()] = i;
area.pop();
stk.pop();
}
area.push(height[i]);
stk.push(i);
}
while (!stk.empty()) {
r[stk.top()] = n;
stk.pop();
area.pop();
}
for (int i = n - 1; i >= 0; --i) {
while (!area.empty() && height[i] < area.top()) {
l[stk.top()] = i;
area.pop();
stk.pop();
}
area.push(height[i]);
stk.push(i);
}
while (!stk.empty()) {
l[stk.top()] = -1;
stk.pop();
area.pop();
}
int ans = 0;
for (int i = 0; i < n; ++i)
chkMax(ans, height[i] * (r[i] - l[i] - 1));
return ans;
}
void solve() {
int ans = 0;
for (int i = 0; i < N; ++i) {
chkMax(ans, solve(H[i]));
}
printf("%d\n", ans);
}
int main() {
scanf("%d", &T);
while (T--) {
input();
solve();
}
return 0;
}
1009. KD-Graph
【题意】
给出一副 n n n个点 m m m条边的带边权无向图,现在要求你确定一个最小的 D D D来将这 n n n个点分为 K K K组,满足:
- 同一组内任意点对存在一条最大边权不超过 D D D的通路
- 不同组内任意点对不存在一条最大边权不超过 D D D的通路
n ≤ 1 0 5 , m ≤ 5 × 1 0 5 n\leq 10^5,m\leq 5\times 10^5 n≤105,m≤5×105
【思路】
显然这个 D D D的取值只可能是某个边权(或 0 0 0),然后 D D D越大, K K K一定不升。
我们从小往大枚举这个 D D D,用并查集合并组,当前的组数恰好为 K K K时就是答案,否则不行。
注意这是一个森林所以最少组数不一定为 1 1 1
【参考代码】
/*
* @date:2021-07-20 14:11:03
* @source:
*/
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {return y > x ? x = y, 1 : 0;}
template<class T, class G> bool chkMin(T &x, G y) {return y < x ? x = y, 1 : 0;}
int T, N, M, K;
map<int, vector<pii>> E;
vi Fa;
int findFa(int x) {
return Fa[x] == x ? x : Fa[x] = findFa(Fa[x]);
}
bool merge(int x, int y) {
x = findFa(x), y = findFa(y);
if (x == y) return false;
Fa[x] = y;
return true;
}
int main() {
scanf("%d", &T);
while (T--) {
E.clear();
scanf("%d%d%d", &N, &M, &K);
int u, v, w;
for (int i = 1; i <= M; ++i) {
scanf("%d%d%d", &u, &v, &w);
E[w].pb({u, v});
}
Fa.resize(N + 1);
for (int i = 1; i <= N; ++i) Fa[i] = i;
int cur = N;
if (K == N) {
printf("%d\n", 0);
continue;
}
bool flag = 0;
Trav(w, E) {
Trav(e, w.sec) {
cur -= merge(e.fir, e.sec);
}
if (cur == K) {
printf("%d\n", w.fir);
flag = 1;
break;
} else if (cur < K) {
printf("%d\n", -1);
flag = 1;
break;
}
}
if (!flag) puts("-1");
}
return 0;
}
1010. zoto
【题意】
给定平面上 n n n个点,第 i i i个点坐标为 ( i , f [ i ] ) (i,f[i]) (i,f[i]),再给出 m m m个矩形,问每个矩形中有多少个 y y y不同的点。
n , m , f [ i ] ≤ 1 0 5 n,m,f[i]\leq 10^5 n,m,f[i]≤105
【思路】
首先如果只是问不同的 y y y的点,那么是一个典中典的扫描线问题,但是要求不同的,数据结构其实并不太好做,但是可以考虑另一个典中典的问题——询问区间中不同的数字个数,这个东西是一个莫队问题。
莫队是一个修改 O ( n n ) O(n\sqrt n) O(nn)次,查询 O ( n ) O(n) O(n)次的东西,如果我们对 x x x做莫队,就变成了上面那个问题,我们只需要把单次修改的复杂度降低,均摊到查询上就可以了。
这里观察到 f f f很小,我们可以再对 f f f分块,维护数的个数以及每块内不同的数有多少个。
这其实是一个典中典的分块套分块。
复杂度 O ( ( n + m ) n ) O((n+m)\sqrt n) O((n+m)n)
【参考代码】
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int B, V;
int t, n, m;
int bl[N], blv[N];
int ans[N], sum[N], sump[N], base[N];
struct query {
int id, l, r, a, b;
bool operator < (const query &x) const {
return (bl[l] ^ bl[x.l]) ? bl[l] < bl[x.l] : ((bl[l] & 1) ? r < x.r : r > x.r);
}
} q[N];
void del(int p) {
if (--sump[base[p]] <= 0) --sum[blv[base[p]]];
}
void add(int p) {
if (++sump[base[p]] <= 1) ++sum[blv[base[p]]];
}
int get(int l, int r) {
r = min(r, V);
if (l > V) return 0;
int nl = blv[l] + 1, nr = blv[r] - 1;
int ret = 0;
if (blv[l] == blv[r]) {
for (int i = l; i <= r; ++i) {
ret += (bool)sump[i];
}
} else {
for (int i = nl; i <= nr; ++i) ret += sum[i];
for (int i = l; blv[i] == blv[l] && l <= V; ++i) ret += (bool)sump[i];
for (int i = r; blv[i] == blv[r] && r >= 0; --i) ret += (bool)sump[i];
}
return ret;
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
B = n / sqrt(m) + 1;
memset(sump, 0, sizeof sump);
memset(sum, 0, sizeof sum);
for (int i = 1; i <= n; ++i) {
scanf("%d", &base[i]);
V = max(V, base[i]);
bl[i] = i / B;
}
for (int i = 1, x0, y0, x1, y1; i <= m; ++i) {
scanf("%d%d%d%d", &x0, &y0, &x1, &y1);
q[i].l = x0, q[i].r = x1;
q[i].a = y0, q[i].b = y1;
q[i].id = i;
}
sort(q + 1, q + m + 1);
int l = 1, r = 0; B = sqrt(V) + 1;
for (int i = 0; i <= V; ++i) blv[i] = i / B;
for (int i = 1; i <= m; ++i) {
int a = q[i].a, b = q[i].b;
while (l < q[i].l) del(l++);
while (l > q[i].l) add(--l);
while (r < q[i].r) add(++r);
while (r > q[i].r) del(r--);
ans[q[i].id] = get(a, b);
}
for (int i = 1; i <= m; ++i) {
printf("%d\n", ans[i]);
}
}
return 0;
}
1011. Necklace of Beads
【题意】
有三种RGB三种颜色的珠子要串成长度为 n n n的项链,RB有无限个,G只有 k k k个,问有多少种本质不同的方案。
n , k ≤ 1 0 6 n,k\leq 10^6 n,k≤106
【思路】
不会。
题解长下面这样:
【参考代码】
#include<bits/stdc++.h>
using namespace std;
#define N 1000001
#define LL long long
#define MOD 998244353
LL n,k,ans,fac[N],inv[N],invfac[N],f[N],p[N],vis[N],prime[N],cnt,phi[N];
void pretype()
{
phi[1]=p[0]=fac[0]=invfac[0]=inv[1]=fac[1]=invfac[1]=1;
p[1]=2;
for(LL i=2;i<N;i++)
{
if(!vis[i])prime[++cnt]=i,phi[i]=i-1;
for(LL j=1;j<=cnt && i*prime[j]<N;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
p[i]=p[i-1]*2ll%MOD;
fac[i]=fac[i-1]*i%MOD;
inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
invfac[i]=invfac[i-1]*inv[i]%MOD;
}
}
LL C(LL n,LL m)
{
if(n<0 || m<0 || m>n)return 0;
return fac[n]*invfac[m]%MOD*invfac[n-m]%MOD;
}
LL get(LL n,LL k)
{
f[0]=n&1?0:2;
for(LL m=1;m<=n;m++)
f[m]=(p[m]*(C(n-m,m)+C(n-m-1,m-1))+f[m-1])%MOD;
return f[min(n,k)];
}
int main()
{
pretype();
int T;
scanf("%d",&T);
while(T--)
{
ans=0;
scanf("%lld%lld",&n,&k);
for(LL d=n;d>=1;d--)
if(n%d==0)
ans=(ans+phi[n/d]*get(d,k*d/n))%MOD;
printf("%lld\n",ans*inv[n]%MOD);
}
return 0;
}