T1
Description
对于两个整数k 和m,如果k 和m 的最大公约数为1,则k 和m 互质。给出两个正整
数n 和m(m≤n),定义f(n,m)为1~n!中与m!互质的数的个数。其中n!=1*2*3*..*(n-1)*n。
Task:给定n 和m,要求计算f(n,m)。
Input
本题设多组数据。
输入文件的第一行有一个整数T(1≤T≤100000),表示有T 组数据。
接下来有T 行,每行两个整数n 和m(2≤n≤100000,2≤m≤n)。
Output
输出文件包含T 行,每行一个整数,表示f(n,m)。
由于答案过大,所以你只要输出f(n,m) mod 131071。
131071 是M17(梅森素数,2^17-1)。
思路:
欧拉+筛素数
首先,令n=N!,m=M!
∵M<=N
∴m|n,所以问题化为求1-n中与m互质的数的个数,即(n/m)*phi(m),其正确性是显然的。
如果一个数x< m且和m互质,即gcd(x,m)=1,那么gcd(x+km,m)=1,假设x+km和m不互质,那么一定存在一个m的因数y,使得y也是x+km的因数,即能写成y(x/y+km/y),但是x中没有y这个因子,
∴假设不成立
∴gcd(x+km,m)=1,对于每一个小于m的和m互质的数,都能用这个式子退出剩下的大于m小于等于n且和m互质的数,于是我们得到ans=(n/m)phi(m),这样是否包含了所有的答案呢?
可以知道对于任意一个与m互质的数,减去km一定能得到m以内的一个和m互质的数,因此上式包含了所有的符合要求的数
由于M!是阶乘,所以phi(i)=M!(1-1/p1)(1-1/p2)…(1-1/pk),1到pk是小于等于M的所有素数,O(MloglogM)筛出,约去M!得ans=N!(1-1/p1)(1-1/p2)…(1-1/pk),所有的N!都可以在O(N)的预处理求出,因为要modR,所以需要求p1到pk的逆元,方程inv[i]=(M-M/i)* inv[M%i]%M,O(N)的预处理。
我们观察式子:ans=N!(1-p1/1)(1-p2/1)…(1-pk/1),对于不同的询问,只有N!和k不同,既然能够预处理N!,何不预处理后面的乘积?进行O(M)的预处理,每次回答的复杂度降为O(1)。
代码:
#include <cstdio>
#include <bitset>
using namespace std;
const int N=100005;
const int mod=131071;
bitset<N> zs;
long long jc[N],ans[N],times[N];
void ycl()
{
int i,j;
zs.set();
for (i=2;i<N;i++) if (zs[i]) for (j=i+i;j<N;j+=i) zs[j]=false;
jc[0]=1;
for (i=1;i<N;i++) jc[i]=jc[i-1]*i %mod;
times[1]=1;
for (i=2;i<N;i++)
{
if(i>=mod) break;
times[i]=(mod-mod/i)*times[mod%i]%mod;
}
ans[1]=1;
for (i=2;i<N;i++)
if (zs[i])
{
ans[i]=ans[i-1]*(i-1)% mod;
ans[i]=ans[i]*times[i%mod]% mod;
}
else ans[i]=ans[i-1];
}
int main ()
{
int T;
scanf("%d",&T);
ycl();
int m,n;
while (T--)
{
scanf("%d%d",&n,&m);
long long answer=jc[n]*ans[m]%mod;
printf("%lld\n",answer);
}
return 0;
}
T2
给你一个N*M 的矩阵,矩阵里面的元素要么是正数,要么是负数,它们的绝对值不大
于10000。现在你可以对矩阵进行两种操作:
1、将某一列的元素全部取反。
2、将某一行的元素全部取反。
你可以执行任意次操作。
Task:通过以上两种操作如果可以将整个矩阵的元素全变为正数的话,则输出最少的操
作次数,否则输出“impossible”(不包括双引号)。
Input
输入文件的第一行有两个整数n 和m(1≤n,m≤1000),表示矩阵的大小。
接下来有N 行,每行M 个数,每个数之间有一个空格。
Output
通过以上两种操作如果可以将整个矩阵的元素全变为正数的话,则输出最少的操作次
数,否则输出“impossible”(不包括双引号)。
思路:
贪心+dfs
首先,我们可以预处理出每一行的负数个数和每一个位置的正负
那么我们在每次dfs里,求出最多负数的行或列(一个)
将这行或列取反,算出取反后的负数个数(如果说是行,会影响到当前列上的值;如果是列,会影响到当前行上的值)和每一个位置的正负
如果每次都判断一遍整个矩阵有没有负数,绝逼会TLE,那么就可以预处理出一个矩阵的负数个数,每次取反便改变
如果在800步内没有做出,输出impossible
代码:
var f:array[0..1001,0..1001]of boolean;
ans,n,m,all,i,j,x,max,maxn,maxm:longint;
h,l:array[0..1001]of longint;
procedure init;
begin
ans:=100000000;
read(n,m);
for i:=1 to n do
begin
for j:=1 to m do
begin
read(x);
if x<0 then
begin
f[i][j]:=false;
inc(h[i]); inc(l[j]);
inc(all);
end
else f[i][j]:=true;
end;
end;
end;
procedure doit;
begin
for i:=1 to n do if (max<h[i])then begin max:=h[i]; maxn:=i; maxm:=1; end;
for i:=1 to m do if (max<l[i])then begin max:=l[i]; maxn:=i; maxm:=2; end;
end;
procedure dfs(temp:longint);
var i:longint;
begin
if (all=0) then
begin
if (temp<ans) then ans:=temp;
exit;
end;
if (temp>800) then
begin
write('impossible');
halt;
end;
max:=0;maxn:=0;maxm:=0;
doit;
if (maxm=1) then
begin
h[maxn]:=m-h[maxn];
for i:=1 to m do
if (f[maxn][i]) then
begin
f[maxn][i]:=false;
inc(l[i]);
inc(all);
end
else
begin
f[maxn][i]:=true;
dec(l[i]);
dec(all);
end;
dfs(temp+1);
h[maxn]:=m-h[maxn];
for i:=1 to m do
if (f[maxn][i]) then
begin
f[maxn][i]:=false;
inc(l[i]);
inc(all);
end
else
begin
f[maxn][i]:=true;
dec(l[i]);
dec(all);
end;
end
else
begin
l[maxn]:=n-l[maxn];
for i:=1 to n do
if (f[i][maxn]) then
begin
f[i][maxn]:=false;
inc(h[i]);
inc(all);
end
else
begin
f[i][maxn]:=true;
dec(h[i]);
dec(all);
end;
dfs(temp+1);
l[maxn]:=n-l[maxn];
for i:=1 to n do
if (f[i][maxn])then
begin
f[i][maxn]:=false;
inc(h[i]);
inc(all);
end
else
begin
f[i][maxn]:=true;
dec(h[i]);
dec(all);
end;
end;
end;
begin
init;
dfs(0);
write(ans);
end.
T3
Description
现在给你M 根柱子,初始的时候有N 个大小不一样的盘插在第一根柱子上面。同
样地,规格大的盘子不能放在规格比它小的盘子上面。问最少需要多少次的移动才能将
这N 个盘从第一根柱子移动到最后一根柱子上面?
Input
输入文件的第一行有两个整数n,m(1≤n≤100000,3≤m≤10),分别表示有n 个盘子和m
根柱子。
Output
输出文件只有一行,一个整数,表示最少的移动次数。保证这个移动次数不会超过
2^63-1。
思路:
DP
设f[i][j]为i个盘子j根柱子的最小移动步数
状态转移方程为2*f[k][j]+f[i-k][j-1]
(其实这个方程有点像分治)意思就是,将一个几个盘子分成两份
代码:
var f:array[0..1001,0..1001]of boolean;
ans,n,m,all,i,j,x:longint;
h,l:array[0..1001]of longint;
procedure init;
begin
ans:=100000000;
readln(n,m);
for i:=1 to n do
begin
for j:=1 to m do
begin
read(x);
if x<0 then
begin
f[i][j]:=false;
inc(h[i]); inc(l[j]);
inc(all);
end else f[i][j]:=true;
end;
end;
end;
procedure dfs(temp:longint);
var i,max,maxn,maxm:longint;
begin
if (all=0) then
begin
if (temp<ans) then ans:=temp;
exit;
end;
if (temp>800) then
begin
write('impossible');
halt;
end;
for i:=1 to n do if (max<h[i])then begin max:=h[i]; maxn:=i; maxm:=1; end;
for i:=1 to m do if (max<l[i])then begin max:=l[i]; maxn:=i; maxm:=2; end;
if (maxm=1) then
begin
h[maxn]:=m-h[maxn];
for i:=1 to m do
if (f[maxn][i]) then
begin
f[maxn][i]:=false;
inc(l[i]);
inc(all);
end else
begin
f[maxn][i]:=true;
dec(l[i]);
dec(all);
end;
dfs(temp+1);
h[maxn]:=m-h[maxn];
for i:=1 to m do
if (f[maxn][i]) then
begin
f[maxn][i]:=false;
inc(l[i]);
inc(all);
end else
begin
f[maxn][i]:=true;
dec(l[i]);
dec(all);
end;
end else
begin
l[maxn]:=n-l[maxn];
for i:=1 to n do
if (f[i][maxn]) then
begin
f[i][maxn]:=false;
inc(h[i]);
inc(all);
end else
begin
f[i][maxn]:=true;
dec(h[i]);
dec(all);
end;
dfs(temp+1);
l[maxn]:=n-l[maxn];
for i:=1 to n do
if (f[i][maxn])then
begin
f[i][maxn]:=false;
inc(h[i]);
inc(all);
end else
begin
f[i][maxn]:=true;
dec(h[i]);
dec(all);
end;
end;
end;
begin
init;
dfs(0);
writeln(ans);
end.