T1
我们可以轻松的发现,对于任意一个青蛙的a[i]来说,它在m中能控制的点均为gcd(a[i],m)的倍数,换句话说就是一个首项为0,末项为小于m的最大项,公差为gcd(m,a[i])的等差数列
因此对于30%的数据来说,我们可以直接跑暴力
而当m较大的时候,我们没有办法将其整个跑gcd,那么我们考虑分解m的因数,然后对于每一个因子分别跑暴力。
而有一些gcd会被重复计算,但是我们发现在1e9以内分解的质因数数量不超过15个,所以我们可以直接二进制表示状态,用容斥的原理将重复计算的gcd减去。
代码很短
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
const int MAXN=1e5+5;
int n,m,T;
int a[MAXN];
int g[MAXN];
int hav[MAXN];
int cnt=0;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
LL lcm(int x,int y)
{
return 1ll*x/gcd(x,y)*y;
}
LL get_sum(int factor)
{
return 1ll*(0+m)*(m/factor+1)/2-m;
}
int main()
{
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
std::scanf("%d",&T);
int cas=0;
while(T--)
{
cas++;
std::memset(hav,0,sizeof(hav));
std::scanf("%d%d",&n,&m);
cnt=0;
for(int i=1;i<=n;i++)
{
std::scanf("%d",a+i);
g[i]=gcd(a[i],m);
}
std::sort(g+1,g+1+n);
for(int i=1;i<=n;i++)
{
bool flag=1;
for(int j=1;j<=cnt;j++)
{
if(g[i]%hav[j]==0)
{
flag=0;
break;
}
}
if(flag)
{
hav[++cnt]=g[i];
}
}
LL S=1<<cnt;
LL ans=0;
for(int s=1;s<S;s++)
{
int flag=-1;
LL now=1;
for(int i=1;i<=cnt;i++)
{
if(s>>i-1&1)
{
flag*=-1;
now=1ll*lcm(hav[i],now);
}
}
ans+=1ll*flag*get_sum(now);
}
std::printf("Case #%d: %I64d\n",cas,ans);
}
return 0;
}
T2
看到数据范围一秒发现是数位dp的裸题。
我们发现直接枚举数字的话会非常的大,那么我们可以考虑到,模数是二进制位下1的个数,而1e19不会超过2^64,所以模数只有64个,那么我们以模数的大小来确定dp方程
个人比较喜欢记忆化搜索的数位dp方式(其实是因为写不来for循环23333...)
我们先枚举模数MOD,则MOD为最终答案中1的个数,再定义dp[dep][up][num][rest]四位分别表示的是当前枚举到的数字到了第几个位数(我们要枚举二进制下的模数,所以存下的是二进制表示下的原数字),up表示上一位是否顶了上界,num表示迄今为止已经有了几个1(当最后的时候num==MOD,说明这个数字合法),rest表示当前数字再模MOD意义下的余数。
直接搜索即可
当然有一个小细节,long long 的最大范围是1e18,所以说1e19的最后一组数据要使用unsigned long long
#include<cstdio>
#include<iostream>
#include<cstring>
#define ULL unsigned long long
ULL dp[66][2][66][66];
int digit[66];
int MOD;
ULL dfs(int dep,int up,int num,int rest)
{
if(!dep)
{
if(num==MOD&&!rest)
{
return 1;
}
else
{
return 0;
}
}
if(dp[dep][up][num][rest]!=-1)
{
return dp[dep][up][num][rest];
}
ULL tmp=0;
for(int t=up==1?digit[dep]:1;t>=0;t--)
{
tmp+=dfs(dep-1,up&&t==digit[dep],num+(t==1),((rest<<1)+(t==1))%MOD);
}
return dp[dep][up][num][rest]=tmp;
}
ULL solve(ULL a)
{
ULL s=a;
int cnt=0;
while(s)
{
digit[++cnt]=s&1;
s>>=1;
}
ULL ans=0;
for(MOD=1;MOD<=64;MOD++)
{
std::memset(dp,-1,sizeof(dp));
ans+=dfs(cnt,1,0,0);
}
return ans;
}
int main()
{
//std::freopen("b.in","r",stdin);
//std::freopen("b.out","w",stdout);
ULL n;
std::cin>>n;
std::cout<<solve(n)<<std::endl;
return 0;
}
T3
看起来非常非常高端的一道题
但是稍稍观察就会发现我们的斜边平方可以转换成两条直角边的平方和,所以我们直接把每行每列的平方和乘以权值之和预处理出来,然后作和比较大小即可(被题面吓得不敢做.....)
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
#include<ctime>
#include<stack>
#include<string>
#define LL long long
const int MAXN=1005;
const LL INF=1e18;
int __,___,____;
int __0,__9;
LL ansx[MAXN];
LL ansy[MAXN];
int x,y,ans;
LL a[MAXN][MAXN];
LL h[MAXN];
LL v[MAXN];
int n,m;
int main()
{
//std::freopen("c.in","r",stdin);
//std::freopen("c.out","w",stdout);
std::scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
std::scanf("%I64d",a[i]+j);
v[i]+=a[i][j];
h[j]+=a[i][j];
}
}
for(int i=0;i<=n;i++)
{
for(int j=i-1,x=2;j>=0;j--,x+=4)
{
ansx[i]+=v[j]*x*x;
}
for(int j=i,x=2;j<n;j++,x+=4)
{
ansx[i]+=v[j]*x*x;
}
}
for(int i=0;i<=m;i++)
{
for(int j=i-1,x=2;j>=0;j--,x+=4)
{
ansy[i]+=h[j]*x*x;
}
for(int j=i,x=2;j<m;j++,x+=4)
{
ansy[i]+=h[j]*x*x;
}
}
LL ans=INF;
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
if(ans>ansx[i]+ansy[j])
{
ans=ansx[i]+ansy[j];
x=i;
y=j;
}
}
}
std::printf("%I64d\n",ans);
std::printf("%d %d\n",x,y);
return 0;
}