A. A/B
要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1)。
思路:A/B求模 相当于1/B与A的乘法求模 获得1/B即对B求逆元
获得逆元的方法:费马小定理(较简单 适用范围小)
根据费马小定理 : 如果p是质数 而且 a与p互质 则有a ^ ( p - 1 ) ≡ 1 (% p)
等式两边同时除以a 可得:a ^ ( p - 2 ) ≡ a ^ ( -1 ) (% p) 即在这种条件下的逆元为 a ^ ( p - 2 )
我们需要对 (A * B ^(-1)) % 9973 求解
(A * B ^(-1)) % 9973 = A % 9973 * (B ^ ( -1 )) % 9973 = n * (B ^ ( 9971 )) % 9973 用快速幂就可以求解
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int mod = 9973;
ll t, n, b;
ll ksm(ll a, ll b)
{
ll ans = 1;
while (b)
{
if (b & 1)
ans = ((ans % mod) * (a % mod)) % mod;
a = ((a % mod) * (a % mod)) % mod;
b >>= 1;
}
return ans % mod;
}
ll ny(ll a){return ksm(a, mod - 2) % mod;}
int main()
{
cin >> t;
while (t--)
{
cin >> n >> b;
printf("%lld\n", ((ny(b) % mod) * (n % mod) % mod));
}
return 0;
}
B Leading and Trailing
大意:给我们若干个数字n和它的幂次k 求其计算结果的前三位和后三位
思路:对于后三位其结果为快速幂求模 前三位可以用一些奇怪的数学知识(科学计数法的思路)
当我们把结果 ( n ^ k ) 用科学计数法表示为 ( n ^ k ) = ( a.bc ) * 10 ^ x 时 对两边取对数
可以得到 k * lg ( n ) = x ( 整数部分 ) + lg ( a.bc )(小数部分)
则 k * lg ( n ) - (下取整)k * lg ( n ) 就可以获得 lg(a.bc) 最后将a.bc解出 乘以100 就是前三位数字
代码:
#include
#include
#include
using namespace std;
typedef long long ll;
const int mod = 1e3;
ll t,cnt;
ll ksm(ll a,ll b)
{
ll ans = 1;
while(b)
{
if(b & 1)
ans = ((ans % mod) * (a % mod)) % mod;
a = ((a % mod) * (a % mod)) % mod;
b >>= 1;
}
return ans;
}
int main()
{
scanf("%lld",&t);
while(t --)
{
cnt ++;
ll n,k,ans1,ans2;
scanf("%lld%lld",&n,&k);
ans2 = ksm(n,k) % mod;
double m = k * log10(n) - (ll)(k * log10(n));
m = pow(10.0, m);
ans1 = m * 100;
printf("Case %lld: %lld %03lld\n",cnt,ans1,ans2);
}
return 0;
}
C 青蛙的约会
题意:在一个已知长度的圈上有两个青蛙在两个点(可以是同一个)上,他们每次可以跳的距离给出,求最少几次二者相遇
思路:用exgcd解不定方程最小的解 之前的博客有些过 丢个链接 戳我
D 斐波那契数列
输入斐波那契数列的前两个数f1,f2,和范围n。输出在n范围内有多少斐波那契数。
思路:裸的fib递推就可以过 也可以用矩阵快速幂优化。
#include
#include
#include
using namespace std;
int f[40];
int main()
{
f[1] = f[2] = 1;
for(int i = 3;i <= 30;i ++)
f[i] = f[i-1] + f[i-2];
int f1,f2,n,p1,p2;
while(scanf("%d%d%d",&f1,&f2,&n) != EOF)
{
for(int i = 2;i <= 30;i ++)
{
if(f[i] == f2) p2 = i;
else if(f[i] == n)
{
p1 = i;
break;
}
else if(f[i] > n)
{
p1 = i - 1;
break;
}
}
printf("%d\n",p1 - p2 + 2);
}
return 0;
}
E.
题意:给定n种长方体的长宽高 摆放时需满足后面摆放的长宽均大于前面的 将这些长方体摆放后的最大高度
思路:求同时满足两个条件的最长上升子序列
我们注意的是 长方体可以同时产生六种变化 每次摆放均需要考虑 实际上最多存在6 * n种长方体
对所有长方体的所有情况按照长宽递增排序 求解最长上升子序列
状态:dp[ i ] 表示 第i个长方体为终点时的最大高度
方程:dp[ i ] = max ( dp[ j ] ) + 第i个长方体的高度 (j < i)
#include
#include
#include
#include
using namespace std;
const int maxn = 23333;
int dp[maxn];
struct poi{
int x,y,z;
}a[maxn];
bool cmp(poi a,poi b)
{
if(a.x == b.x ) return a.y < b.y;
return a.x < b.x;
}
int main()
{
int n,len,cnt = 1;
int xx,yy,zz;
while(scanf("%d",&n) && n)
{
len = 0;
for(int i = 0;i < n;i ++)
{
cin >> xx >> yy >> zz;
a[len].x = xx,a[len].y = yy,a[len++].z = zz;
a[len].x = xx,a[len].y = zz,a[len++].z = yy;
a[len].x = yy,a[len].y = xx,a[len++].z = zz;
a[len].x = yy,a[len].y = zz,a[len++].z = xx;
a[len].x = zz,a[len].y = xx,a[len++].z = yy;
a[len].x = zz,a[len].y = yy,a[len++].z = xx;
}
sort(a,a + len,cmp);
dp[0] = a[0].z;
int maxx;
for(int i = 1;i < len;i ++)
{
maxx = 0;
for(int j = 0;j < i;j ++)
{
if(a[j].x < a[i].x && a[j].y < a[i].y)
maxx = max(maxx,dp[j]);
}
dp[i] = a[i].z + maxx;
}
maxx = 0;
for(int i = 0;i < len;i ++)
if(maxx < dp[i]) maxx = dp[i];
printf("Case %d: maximum height = %d\n",cnt ++,maxx);
}
return 0;
}
F.
题意:给定一根长度已知的木棍和需要切割的位置 每次切割的花费为该位置所在段的木棍长度 求最小花费
思路:
区间DP 枚举中间点k
dp[i][j]表示当前(i,j)区间 切割k处的最小花费
方程 dp[i][j] = min( dp[i][k] + dp[k][j] + num[j] - num[i] ) ;
#include
#include
#include
#define INF 1e9
using namespace std;
int dp[233][233];
int num[233];
int main()
{
int n,L,j,minn;
while(scanf("%d",&L) && L)
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++) scanf("%d",&num[i]);
num[0] = 0,num[n + 1] = L;
memset(dp,0,sizeof(dp));
for(int l = 1;l <= n + 1;l ++)
{
for(int i = 0;i <= n + 1;i ++)
{
j = i + l;
minn = INF;
if(j > n + 1) break;
for(int k = i + 1;k < j;k ++)
{
int tmp = dp[i][k] + dp[k][j] + num[j] - num[i];
if(minn > tmp) minn = tmp;
}
if(minn != INF) dp[i][j] = minn;
}
}
printf("The minimum cutting is %d.\n",dp[0][n + 1]);
}
return 0;
}
G
题意:给定 1- 6 6种价值的物品的个数 问是否可以将这六个物品均分 使两个人获得的价值相等
思路:
因为物品个数不确定 所以在取用物品的时候也要考虑完全背包
01 + 完全背包 采用混合背包求解
dp[i] 表示背包容量为 i 时是否取用当前物品的最大价值 物品的体积为vi 价值为wi
方程:dp[i] = max(dp[i],dp[i - vi] + wi);
#include
#include
#include
#include
using namespace std;
const int maxn = 233333;
int dp[maxn];
int num[10],v,flag;
void zopack(int vi,int wi)
{
for(int i = v;i >= vi;i --)
{
dp[i] = max(dp[i],dp[i - vi] + wi);
if(dp[i] == v)
{
flag = 1;
return;
}
}
return ;
}
void compack(int vi,int wi)
{
for(int i = vi;i <= v;i ++)
{
dp[i] = max(dp[i],dp[i - vi] + wi);
if(dp[i] == v)
{
flag = 1;
return;
}
}
return ;
}
void mixpack(int vi,int wi,int num)
{
if(vi * num >= v) compack(vi,wi);
if(flag) return ;
int k = 1;
while(k <= num)
{
zopack(vi * k,wi * k);
if(flag) return ;
num -= k;
k <<= 1;
}
return ;
}
int main()
{
int n,tot = 1;
while(true)
{
int sum = 0;
flag = 0;
for(int i = 1;i <= 6;i ++)
{
scanf("%d",&num[i]);
sum += i * num[i];
}
if(sum == 0) break;
if(sum % 2)
{
printf("Collection #%d:\nCan't be divided.\n\n",tot++);
continue;
}
else
{
memset(dp,-1,sizeof(dp));
dp[0] = 0;
v = sum / 2;
for(int i = 1;i <= 6;i ++) mixpack(i,i,num[i]);
if(flag == 1)
{
printf("Collection #%d:\nCan be divided.\n\n",tot++);
continue;
}
else
{
printf("Collection #%d:\nCan't be divided.\n\n",tot++);
continue;
}
}
}
return 0;
}
H
题意:给定一个数 将其分成几个不同的数 求解一共有几种分法
思路:dp[i][j]为前i个数组成j的方案数.
方程:dp[i][j] = sum(dp[i - 1][j - i]).意思是前i - 1个数组成j - i的方案数.
可以用滚动数组优化,本质上其实就是一个01背包,容量为j,重量为i
滚动方程:dp[j] = sum(dp[j - i])
最后答案应减去初始状态 dp[0]
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 505;
ll dp[maxn];
int main()
{
dp[0] = 1;
for(int i = 1;i <= maxn;i ++)
for(int j = 500;j >= i;j --)
dp[j] += dp[j - i];
int n;
while(scanf("%d",&n) && n) printf("%lld\n",dp[n] - 1);
return 0;
}
I
题意:在一个迷宫里可以从任一点出发 每次可以向四面低的格子滑行 求最长滑行距离
思路:
记忆化搜索 用每次搜索的结果更新dp数组
只要目标点满足下滑条件就搜索
dp[i][j]表示 从(i,j)出发的最优解
#include
#include
#include
#include
using namespace std;
const int maxn = 1001;
int ditu[maxn][maxn];
int n, m, dp[maxn][maxn];
int fx[5] = { 0, 0, 1, 0, -1 };
int fy[5] = { 0, 1, 0, -1, 0 };
bool check(int x, int y)
{
if (x >= 1 && x <= n && y >= 1 && y <= m)
return true;
return false;
}
int dfs(int x, int y)
{
if (dp[x][y])
return dp[x][y];
int step = 0;
for (int i = 1; i <= 4; i++)
{
int gx = x + fx[i];
int gy = y + fy[i];
if (check(gx, gy) && ditu[gx][gy] > ditu[x][y])
{
dp[gx][gy] = dfs(gx, gy);
step = max(step, 1 + dfs(gx, gy));
}
}
dp[x][y] = step;
return step;
}
int main()
{
int ans = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &ditu[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (!dp[i][j]) dfs(i, j);
ans = max(ans, dp[i][j]);
}
printf("%d", ans + 1);
return 0;
}
J
题意:括号匹配 求序列最大匹配长度
思路:dp[i][j]表示区间(i,j)的最大匹配长度
如果i,j是匹配的 则长度会更新 dp[i][j] = dp[i + 1][j - 1] + 2;
每次枚举 中间结点k dp[i][j] = max(dp[i][j],dp[i][k] + dp[k + 1][j])
#include
#include
#include
#include
using namespace std;
const int maxn = 1005;
char a[maxn];
int dp[maxn][maxn];
bool check(char a,char b)
{
if(a == '(' && b == ')') return true;
else if(a == '[' && b == ']') return true ;
else return false;
}
int main()
{
while(scanf("%s",&a))
{
int maxx = 0,j;
if(a[0] == 'e') break;
memset(dp,0,sizeof(dp));
int n = strlen(a);
for(int l = 1;l <= n;l ++)
{
for(int i = 0;i <= n;i ++)
{
j = i + l;
if(check(a[i],a[j])) dp[i][j] = dp[i + 1][j - 1] + 2;
else dp[i][j] = dp[i + 1][j];
for(int k = i + 1;k < j;k ++)
dp[i][j] = max(dp[i][j],dp[i][k] + dp[k + 1][j]);
}
}
printf("%d\n",dp[0][n]);
}
return 0;
}