QDU_ACM集训队_暑假训练第一周(一)

  1. HDU 1576 A / B 费马小定理求解逆元
  2. Light OJ 1282 Leading and Trailing 快速幂 + 数学知识
  3. POJ 1061 青蛙的约会 exgcd求不定方程的最小解
  4. HRBUST 2083 斐波那契数列 可以用各种求fib的方法乱搞
  5. HDU 1069 Monkey and banana 最长上升子序列
  6. UVA 10003 Cutting Sticks 区间DP
  7. POJ 1014 Dividing 多重背包DP
  8. ZOJ 1163 The Staircases 背包DP
  9. POJ 1088 滑雪 记忆化搜索
  10. POJ 2955 Brackets 区间DP

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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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<iostream>
#include<cstdio>
#include<cmath>
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<iostream>
#include<cstdio>
#include<cstring>
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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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<iostream>
#include<cstdio>
#include<cstring>
#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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值