2024-03-21

2024-03-21

Grass Cownoisseur G

上周没写完的题

分析过思路了,直接放码

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>

using namespace std;

const int N=100100,M=2*N;

int n,m;

int hd[N],edg[N],nxt[N],idx;

void adde(int u,int v) {
	edg[idx]=v,nxt[idx]=hd[u],hd[u]=idx++;
}

pair<int,int> es[N];

vector<int> G[M];

int scc[N],siz[M],cnt;
int dfn[N],low[N],idt;
int stk[N],top;
bool ins[N];

void Tarjan(int u) {
	dfn[u]=low[u]=++idt;
	stk[++top]=u,ins[u]=true;
	for(int i=hd[u];~i;i=nxt[i]) {
		int v=edg[i];
		if(!dfn[v]) {
			Tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]) {
		cnt++;
		while(stk[top]!=u) {
			scc[stk[top]]=cnt;
			siz[cnt]++;
			ins[stk[top]]=false;
			top--;
		}
		scc[u]=cnt;
		siz[cnt]++;
		ins[u]=false;
		top--;
	}
}

int dist[M];
bool inq[M];

void spfa() {
	queue<int> que;
	que.push(scc[1]);
	inq[scc[1]]=true;
	while(!que.empty()) {
		int u=que.front();
		que.pop();
		inq[u]=false;
		for(int i=0;i<G[u].size();i++) {
			int v=G[u][i];
			if(dist[v]<dist[u]+siz[u]) {
				dist[v]=dist[u]+siz[u];
				if(!inq[v]) {
					que.push(v);
					inq[v]=true;
				}
			}
		}
	}
}

int main() {
	memset(hd,-1,sizeof(hd));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		adde(u,v);
		es[i].first=u,es[i].second=v;
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);
	for(int i=1;i<=cnt;i++) siz[i+cnt]=siz[i];
	for(int i=1;i<=m;i++) {
		int u=es[i].first,v=es[i].second;
		if(scc[u]==scc[v]) continue;
		G[scc[u]].push_back(scc[v]);
		G[scc[u]+cnt].push_back(scc[v]+cnt);
		G[scc[v]].push_back(scc[u]+cnt);
	}
	G[scc[1]].push_back(scc[1]+cnt);
	spfa();
	printf("%d\n",dist[scc[1]+cnt]);
	
	return 0;
} 

听课的时候老师讲的
终于知道 C++ 负数取模是负数的原因了
因为 C++ 整除是 向 0 取整 Python 等 是 向负无穷取整
意思就是 C++ 向靠近 0 的那边取整
eg:
C++: 5/3=1 -5/3=-1
Python: 5/3=1 -5/3=-2


说唱

上周考试不会的题,改改

首先理解 \(f(x)\) 是什么意思
例如 \(x=2134\) 那么 \(f(x)=2134+213+21+2\)
换一个形式 \(f(x)=2222+111+33+4\)
我们发现,对于从右往左数第 \(k\) 个数字 \(a_k\) 它对 \(f(x)\) 的贡献是

\[a_k\times\sum_{i=0}^{k-1}10^i \]

等比数列求和一下,其实就是

\[\frac{(10^k-1)\times a_k}{9} \]

把所有位的贡献加起来,得到

\[y=f(x)=\frac{10x-S}{9} \]

其中 \(S\)\(x\) 所有位上数字之和
整理一下

\[10x-9y=S \]

我们记 \(p=10x\) 从第一个大于等于 \(9y\)\(10\) 的倍数开始枚举 \(p\)
注意到 \(x\) 的数字之和就是 \(p\) 的数字之和
每次 \(p\) 加十的时候更新 \(S\) ,再记录 \(p-9y\)\(D\)
若某一时刻 \(S\)\(D\) 相等就得到答案
如果一直枚举到 \(D\) 比 n 位数字所能达到的最大的数字和 \(9n\) 还大都没有找到答案,说明无解

时间复杂度 \(O(n)\)

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=6e5+10;

char s[N];
int x[N],y[N];
int D;
int S;

int n;

int main() {
	int T;
	scanf("%d",&T);
	while(T--) {
		memset(x,0,sizeof(x));
		memset(y,0,sizeof(y));
		D=0,S=0;
		scanf("%s",s+1);
		n=strlen(s+1);
		int nn=n;
		for(int i=1;i<=n;i++) y[i]=s[n-i+1]-'0',x[i]=y[i]*9;
		if(nn==1&&y[1]==0) {
			puts("0");
			continue;
		}
		for(int i=1;i<=n;i++) x[i+1]+=x[i]/10,x[i]%=10;
		while(x[n+1]>9) x[n+2]+=x[++n]/10,x[n]%=10;
		n+=(x[n+1]>0);
		int p=1;
		D+=10-x[p],x[p]+=10-x[p];
		while(x[p]>9) x[p+1]+=x[p]/10,x[p]%=10,p++;
		n=max(n,p);
		for(int i=1;i<=n;i++) S+=x[i];
		bool flg=false;
		while(D<=9*nn) {
			if(S==D) {
				for(int i=n;i>1;i--) printf("%d",x[i]);
				puts("");
				flg=true;
				break; 
			}
			D+=10;
			int p=2;
			x[p]++;
			while(x[p]>9) x[p+1]+=x[p]/10,x[p]%=10,p++;
			n=max(n,p);
			S-=9*(p-2),S++;
		}
		if(!flg) puts("-1");
	}
	
	return 0;
}

ztx 的玄学方法(不知道正不正确)

  • 原题中的递归式如果不向下取整,y=x*10/9,则 x=y*0.9
  • 加上取整误差不超过 \(\log x\) 也就是位数 n
  • f(x) 单调增,可以二分

复杂度 \(O(n\log n)\)

扩展中国剩余定理

数学专题题单上面的
发现自己 72 分

update: 原因是要开 _int128……

exCRT

对于两个方程

\[\left\{\begin{matrix} x\equiv r_1 \ (\bmod \ p_1) \\ x\equiv r_2 \ (\bmod \ p_2) \end{matrix}\right. \]

考虑怎么合并
写成带余除法的形式

\[x=k_1\times p_1+r_1=k_2\times p_2+r_2 \]

移项得到

\[k_1\times p_1-k_2\times p_2=r_2-r_1 \]

用 exgcd 求出 k1 的最小正整数解
新的方程为

\[x\equiv R \ (\bmod \ M) \]

其中$$R=k_1\times p_1+r_1 \ \ \ M=lcm(p_1,p_2)$$

n个方程,两两合并 n-1 次
最后的 R 就是答案

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

typedef long long ll;
typedef __int128 i128;

int n;
ll pin,rin;

i128 exgcd(i128 a,i128 b,i128 &x,i128 &y) {
	if(b==0) {
		x=1,y=0;
		return a;
	}
	i128 d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return d;
}

int main() {
	scanf("%d",&n);
	scanf("%lld%lld",&pin,&rin);
	i128 p1=pin,r1=rin;
	for(int i=1;i<n;i++) {
		scanf("%lld%lld",&pin,&rin);
		i128 p2=pin,r2=rin;
		i128 k1,k2;
		i128 d=exgcd(p1,p2,k1,k2);
		i128 g=r2-r1;
		if(g%d!=0) {
			puts("-1");
			return 0;
		}
		k1=((k1*g/d)%(p2/d)+(p2/d))%(p2/d);
		r1+=p1*k1,p1=(p1*p2)/d;
	}
	printf("%lld\n",r1);
	
	return 0;
}

荒岛野人

原题等价于

给定 \(C_1,C_2,\dots,C_n \ \ \ P_1,P_2,\dots,P_n \ \ \ L_1,L_2,\dots,L_n\)
求最小的 M ,使得对于任意一对 \(i,j\) ,同余方程 \(C_i+xP_i\equiv C_j+xP_j \ (\bmod \ M)\) 都无解或最小正整数解大于 \(\min(L_i,L_j)\)
方程可以化为

\[(P_i-P_j)\times x\equiv C_j-C_i \pmod{M} \]

\(\max C_i\) 开始枚举 \(M\)

然后枚举所有的 \((i,j)\) 用 exgcd 求出最小的正整数解

如果有符合条件的解,那么当前的 M 不行
如果所有 \(n^2\) 个方程都在范围内无解,输出当前的 M

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=17;

int n;
int c[N],p[N],l[N];

int exgcd(int a,int b,int &x,int &y) {
	if(b==0) {
		x=1,y=0;
		return a;
	}     
	int d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return d;
}

bool check(int M) {
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			if(i==j) continue;
			if(p[i]<p[j]) continue;
			int A=p[i]-p[j],B=c[j]-c[i];
			int x,y;
			int d=exgcd(A,M,x,y);
			if(B%d==0) {
				x=((x*(B/d))%(M/d)+(M/d))%(M/d);
				if(x<=min(l[i],l[j])) return false;
			}
		}
	}
	return true;
}

int main() {
	scanf("%d",&n);
	int m=0;
	for(int i=1;i<=n;i++) scanf("%d%d%d",&c[i],&p[i],&l[i]),m=max(m,c[i]);
	while(true) {
		if(check(m)) {
			printf("%d\n",m);
			break;
		}
		m++;
	}
	
	return 0;
}

古代猪文

Lucas定理
若 p 是质数

\[{n\choose m} \bmod \ p={\left\lfloor n/p\right\rfloor\choose\left\lfloor m/p\right\rfloor}\times {n\bmod p\choose m\bmod p}\bmod p \]

# 题意简述 #
计算

\[g^{\sum_{k\mid n}{n\choose k}}\bmod 999911659 \]

# Solution #

数学全家桶
欧拉定理 + 卢卡斯定理 + 中国剩余定理 + 快速幂

  • \(999911659\) 是质数 根据欧拉定理 就是要算 \(g^{\sum_{k\mid n}{n\choose k}\bmod 999911658}\bmod 999911659\)

考虑如何计算 \(\sum_{k\mid n}{n\choose k}\bmod 999911658\)

  • \(999911658=2\times3\times4679\times35617\)

有些题的大数字比较奇怪的时候可以分解一下看看可能会有特殊性质

  • Lucas 求出 \(\sum_{k\mid n}{n\choose k}\) 模每个小质数的结果 \(r_i\)

  • 然后中国剩余定理求解同余方程组

\[\left\{\begin{matrix} x\equiv r_1\pmod2\\ x\equiv r_2\pmod3\\ \ \ \ \ \ \ x\equiv r_3\pmod{4679}\\ \ \ \ \ \ \ \ \ x\equiv r_4\pmod{35617} \end{matrix}\right. \]

得出 \(\sum_{k\mid n}{n\choose k}\bmod 999911658\)

  • 最后快速幂就行
#include<iostream>
#include<cstring>
#include<algorithm>

#define mod 999911658

using namespace std;

typedef long long ll;

const int dd[4]={2,3,4679,35617};

ll n,g;

ll frac[99999];

ll qpow(ll a,ll k,ll p) {
	ll res=1;
	while(k) {
		if(k&1) res=res*a%p;
		a=a*a%p,k>>=1;
	}
	return res;
}

ll C(ll x,ll y,ll p) {
	if(x<y) return 0;
	return frac[x]*qpow(frac[y],p-2,p)%p*qpow(frac[x-y],p-2,p)%p;
}

ll Lucas(ll x,ll y,ll p) {
	if(y==0) return 1;
	return C(x%p,y%p,p)*Lucas(x/p,y/p,p)%p;
}

void init(ll x) {
	frac[0]=1;
	for(ll i=1;i<=x;i++) frac[i]=frac[i-1]*i%x;
}

int r[5];

int main() {
	scanf("%lld%lld",&n,&g);
	if(g%(mod+1)==0) {
		puts("0");
		return 0;
	}
	ll h=0;
	for(int i=0;i<4;i++) {
		ll D=dd[i];
		init(D);
		for(ll d=1;d*d<=n;d++) {
			if(n%d!=0) continue;
			r[i]=(r[i]+Lucas(n,d,D))%D;
			if(d*d==n) continue;
			r[i]=(r[i]+Lucas(n,n/d,D))%D;
		}
		h=(h+r[i]*(mod/D)%mod*qpow(mod/D,D-2,D)%mod)%mod;
	}
	printf("%lld\n",qpow(g,h,mod+1));
	
	return 0;
}

需要注意的点:

  1. 组合数 C(n,m) 在 n<m 的时候要返回 0(不然会RE)
  2. g 是模数的倍数的时候特判输出 0
  3. frac 不能 一开始初始化,各个函数的模数也不能用全局的,而是要传参数,因为 4 个模数情况都不一样要单独考虑
posted @ 2024-03-21 21:21  OrangeStar*  阅读(13)  评论(0编辑  收藏  举报