JZOJ 8.10 B组总结

NO.1

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)。

Sample Input

1
3 2

Sample Output

3

数据约定:
对于50%的数据,T=1,2≤N≤10
对于80%的数据,T=1,2≤N≤100000
对于100%的数据,1≤T≤100000,2≤N≤100000
AcFast 友情提示:小心运算溢出,也就是RTE215 错误。。。


思路:欧拉+筛素数
首先,令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;
}

NO.2

Description

给你一个N*M 的矩阵,矩阵里面的元素要么是正数,要么是负数,它们的绝对值不大
于10000。现在你可以对矩阵进行两种操作:
1、将某一列的元素全部取反。
2、将某一行的元素全部取反。
你可以执行任意次操作。
Task:通过以上两种操作如果可以将整个矩阵的元素全变为正数的话,则输出最少的操
作次数,否则输出“impossible”(不包括双引号)。

Input

输入文件的第一行有两个整数n 和m(1≤n,m≤1000),表示矩阵的大小。
接下来有N 行,每行M 个数,每个数之间有一个空格。

Output

通过以上两种操作如果可以将整个矩阵的元素全变为正数的话,则输出最少的操作次
数,否则输出“impossible”(不包括双引号)。

Sample Input

2 4
3 7 -6 1
-2 -6 8 -4

Sample Output

2

Hint

数据约定:
对于40%的数据,1≤N,M≤10
对于100%的数据,2≤N,M≤1000


思路:贪心+dfs
首先,我们可以预处理出每一行的负数个数和每一个位置的正负
那么我们在每次dfs里,求出最多负数的行或列(一个)
将这行或列取反,算出取反后的负数个数(如果说是行,会影响到当前列上的值;如果是列,会影响到当前行上的值)和每一个位置的正负
如果每次都判断一遍整个矩阵有没有负数,绝逼会TLE,那么就可以预处理出一个矩阵的负数个数,每次取反便改变
如果在800步内没有做出,输出impossible


代码:

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 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;
        max:=0;maxn:=0;maxm:=0;
        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
  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;
  dfs(0);
  writeln(ans);
end.

NO.3

Description

现在给你M 根柱子,初始的时候有N 个大小不一样的盘插在第一根柱子上面。同
样地,规格大的盘子不能放在规格比它小的盘子上面。问最少需要多少次的移动才能将
这N 个盘从第一根柱子移动到最后一根柱子上面?

Input

输入文件的第一行有两个整数n,m(1≤n≤100000,3≤m≤10),分别表示有n 个盘子和m
根柱子。

Output

输出文件只有一行,一个整数,表示最少的移动次数。保证这个移动次数不会超过
2^63-1。

Sample Input

4 3

Sample Output

15

数据约定:
对于30%的数据,M=3
对于80%的数据,1≤N≤100,3≤M≤10
对于100%的数据,1≤N≤100000,6≤M≤10


思路:DP
设f[i][j]为i个盘子j根柱子的最小移动步数
状态转移方程为2*f[k][j]+f[i-k][j-1]
(其实这个方程有点像分治)意思就是,将一个几个盘子分成两份,如下图
这里写图片描述
那么这个决策点k怎么去枚举呢,假设g[i]表示有i个盘子时的决策点,g[i]=g[i-1]
如果当前决策点不够它的+1优,就+1


代码:

#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
unsigned long long f[100010][12];
int g[100010];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for (int j=3;j<=m;j++)
    {
        g[0]=0;
        for (int i=1;i<=n;i++)
        {
            if (j==3) {f[i][j]=f[i-1][j]*2+1; continue;}
            g[i]=g[i-1];
            if (i<j) f[i][j]=i*2-1; else
            {
                int k=g[i];     
                f[i][j]=2*f[k][j]+f[i-k][j-1];
                if (f[k+1][j]*2+f[i-k-1][j-1]<f[i][j]) f[i][j]=f[k+1][j]*2+f[i-k-1][j-1],g[i]++;
            }
        }
    }
    printf("%lld",f[n][m]);
}

NO.4

Description

Z 国是一个拥有N 个岛的国家。这N 个岛用N-1 条桥来连接,且任意两个岛之间都可以互达。
某商人听说Z 国是一个很富有的国家,所以他想到Z 国闯一闯。经过他仔细的观察,他发现某样商品特别受欢迎,而且由于各岛之间沟通联系不够多,所以这样物品在每个岛的价格可能都不同。
Task:商人开始在编号为x 的岛上,然后他要走到编号为y 的岛上。在这期间,他可以在x 岛y 的路径上买一件商品,和卖一件商品。,注意,仅能买一件和卖一件!显然你要计算商人从岛x 到岛y 最多能赚多少钱。

Input

输入第一行有一个整数N(1≤n≤50000),表示Z 国有N 个岛。
接下来有N 行,每行一个整数Ci(1≤Ci≤50000),第N+i 行的Ci 表示商品在岛i 的价
格。
再接下来有N-1 行,每行两个整数x,y(1≤x,y≤50000),表示岛x 和岛y 之间有一条
桥。
接下来有一个整数M,表示有M 个询问。
然后M 行,每行两个整数,x,y(1≤x,y≤50000),表示询问你,商人从岛x 到岛y 最多
能赚多少钱?

Output

对于每次询问,如果商人能赚到钱,则输出最多能赚多少钱。
如果不能赚钱,就输出0(你可以这样理解——亏本生意谁都不会做^_^)

Sample Input

4
1
2
3
4
1 2
1 4
2 3
3
1 3
3 1
1 4

Sample Output

2
0
3

Hint

数据约定:
对于30%的数据, 1≤N,M≤100
对于60%的数据,1≤N,M≤1000
对于100%的数据,1≤N,M≤50000


思路:Tarjan
这里写图片描述


代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,i,next[1000001],last[100001],bridge[100001],tot,a[50001],x[50001],y[50001],ans,k[50001][101][3],b[50001],way[50001];
bool bz[50001];
int find(int t)
{
    if (b[t]==t) return t;
    else return find(b[t]);
}
void Tarjan(int l)
{
    int i=last[l],yy=0;
    bz[l]=true;
    while (i)
    {
        yy=bridge[i];
        if (bz[yy]==false)
        {
            Tarjan(yy);
            b[yy]=l;
        }
        i=next[i];
    }
    for (i=1;i<=k[l][0][0];i++) if (bz[k[l][i][1]]) k[l][i][2]=find(k[l][i][1]);
}
void get(int l,int w)
{
    if (l==w)
    {
          way[++way[0]]=l;
          return;
    }
    else 
    {
         way[++way[0]]=l;
         get(b[l],w);
         return;
    }
}
void get2(int l,int w)
{
    while (l==w)
    {         
              get(b[l],w);
              way[++way[0]]=l;
              return;
    }
} 
int main()
{
    int x1,y1;
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=i;
    }
    for (int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&x1,&y1);
        tot++; bridge[tot]=y1; next[tot]=last[x1]; last[x1]=tot;
        tot++; bridge[tot]=x1; next[tot]=last[y1]; last[y1]=tot;
    }
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&x[i],&y[i]);
        k[x[i]][++k[x[i]][0][0]][1]=y[i];
        k[y[i]][++k[y[i]][0][0]][1]=x[i];
    }
    Tarjan(1);
    int w=0;
    for (int i=1;i<=m;i++)
    {
        for (int j=1;j<=k[x[i]][0][0];j++)
            if (k[x[i]][j][1]==y[i])
            {
                                      w=k[x[i]][j][2];
                                      break;
            }
        if (w==0)
        {
                 for (int j=1;j<=k[y[i]][0][0];j++)
                     if (k[y[i]][j][1]==x[i])
                     {
                                      w=k[y[i]][j][2];
                                      break;
                     }     
        }
        way[0]=0;
        get(x[i],w);get2(y[i],w);
        int mx[50001],mn[50001],ans=0;
        mn[0]=2147483647;mx[way[0]+1]=0;
        for (int j=1;j<=way[0];j++) mn[j]=min(mn[j-1],a[way[j]]);   
        for (int j=way[0];j>=1;j--) mx[j]=max(mx[j+1],a[way[j]]);   
        for (int j=2;j<=way[0];j++) ans=max(ans,mx[j]-mn[j-1]);
        printf("%d\n",ans);
    }
}

转载于:https://www.cnblogs.com/Comfortable/p/8412258.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值