【总结】齐朋辉の四道题

本来说昨天吧四道题切完的……结果尧神来了。。窝就不淡定了。。。。 = =各种写不出(其实还是我太弱。。)


T1 方老师字符串



【题面描述】

方老师喜欢一种字符串,包含以下两个条件:


包含且仅包含N个0和M个1
通过压缩变换之后可以得到G
压缩变换这样定义:如果这个字符串长度大于等于2,那么


如果最后两个字符是两个0,将这两个0替换为1 eg. 0100 -> 011
如果最后两个字符不全是0,将这两个字符替换为0 eg. 01010 -> 0100.
现在方老师想知道有多少满足条件的字符串。(对1000000007取模)


【Input】

三个数 N,M和G。
N,M≤10^5,N+M≥1,0≤G≤1


【Output】

一个数表示有多少个字符串(对1000000007取模)


【样例输入】

2 2 0


【样例输出】

4



题解

设dp[i][j][k]表示i个0 j个1 从k扩展出来的字符串的数目

转移:dp[n][m][0] = dp[n - 1][m][1] + dp[n][m - 1][0] + dp[n][m - 1][1]

dp[n][m][1] = dp[n - 1][m][0]

由排列组合可得:dp[n][m - 1][0] + dp[n][m - 1][1] = C(n + m - 1, n)

所以:dp[n][m][0] = dp[n - 1][m][1] + C(n + m - 1, n)
dp[n][m][1] = dp[n - 1][m][0]

于是:dp[n][m] = dp[n - 2][m] + C(n + m - 1, n) (从0扩展)

从1扩展时n--即可

其中:m为不变量 省去 即 dp[n] = dp[n-2] + C(n + m - 1, n) 

边界还有特判什么的……打表就出来了QAQ


扩展欧几里得求逆元: ax + bp = 1 其中a 为 x mod p 的逆元


当时本来是想出了dp方程的 但是不知道怎么降维orz 研究了很久还是搜索交表了……搜出来的数据很小但是竟然拿了75分。。


code


#include <cstdio>
#include <iostream>

using namespace std;

const long long mod = 1000000007ll;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

int N, M, G;
long long dp[100005]; // 有i个0 
long long ad[200005]; // 阶乘 

inline void pre()
{

	if(M == 1) dp[0] = 0;
	else dp[0] = 1;
	if(M <= 1) dp[1] = M + 1;
	else dp[1] = M;
	
	ad[0] = 1ll;
	for(int i = 1; i <= 200000; ++i)
	{
		ad[i] = ( ad[i - 1] * i ) % mod;
	}
}

void ex_gcd(long long a, long long b, long long &x, long long &y)
{
	if(!b) { x = 1; y = 0; return; }
	ex_gcd(b, a % b, y, x);
	y -= a / b * x;
} 

long long C(int n, int m)
{
	long long x, y;
	ex_gcd(ad[m] * ad[n - m] % mod, mod, x, y);
	return (ad[n] * (x + mod) % mod) % mod;
}

int main()
{
	freopen("A.in", "r", stdin);
	freopen("A.out", "w", stdout);
	
	N = read(); M = read(); G = read();
	
	if(G) N--;
	
	if(N == -1)
	{
		if(M == 1) puts("1");
		else puts("0");
		return 0;
	}
	
	if(!N)
	{
		if(M <= 1) puts("0");
		else puts("1");
		return 0;
	}
	
	if(!M)
	{
		if(N & 1) puts("1");
		else puts("0");
		return 0;
	}
	
	if(N == 1)
	{
		if(M <= 1) printf("%d\n", M + 1);
		else printf("%d\n", M);
		return 0;
	}
	
	pre();
	for(int i = 2; i <= N; ++i)
	{
		dp[i] = ( dp[i - 2] + C(i + M - 1, i) ) % mod;
	}
	
	cout << dp[N] << endl;
	
	return 0;
}

T2 GCD and LCM

【题面描述】

给两个正整数G和L,求有多少个有序三元组(x, y, z),满足gcd(x, y, z) = G且lcm(x, y, z) = L。
其中gcd(x, y, z)表示x, y, z 的最大公约数, lcm(x, y, z)表示x, y, z的最小公倍数。
注意:(1, 2, 3)和(1, 3, 2)是两个不同的三元组


【输入】

读入两个整数G、L
1 <= G, L <= 1 000 000 000

【输出】

输出答案。

【样例输入】

6 72

【样例输出】

72


题解

可转化为gcd(x / G, y / G, z / G) = 1 and lcm(x / G, y / G, z / G) = L / G
将 L / G 质因数分解,设某一个素因子p的幂次为k 显然x / G, y / G, z / G中关于p的幂次至少有一个为0 至少有一个k
所以对于每一个幂次为k的素因子p 对答案的贡献为ans = ans * 6 * k

考试的时候直接把L和G都分解了 码的很快也没检查 = =因为急orz 也没特判……其实我的方法没写挂的话应该有90分……结果只水过了4条数据……orz

code

#include <cstdio>
#include <iostream>
#include <cmath>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

int g, l;
int pr[100005];
int cnt;
int a[100005];

long long ans = 1ll;

inline void getp(int t)
{
	int lim = sqrt(t);
	for(int i = 2; i <= lim; ++i)
	{
		if(t % i == 0)
		{
			pr[++cnt] = i;
			while(t % i == 0)
			{
				t /= i;
				++a[cnt];
			} 
		}
	}
	if(t != 1)
	{
		pr[++cnt] = t; 
		a[cnt] = 1; 
	} 
}

long long mul(int a, int b)
{
	long long temp = 1ll, cmp = a;
	while(b)
	{
		if(b & 1) temp *= cmp;
		b >>= 1;
		cmp *= cmp;
	}
	return temp;
}

int main()
{
	freopen("B.in", "r", stdin);
	freopen("B.out", "w", stdout);
	
	g = read(); l = read();
	if(l % g)
	{
		puts("0");
		return 0;
	}
	
	l /= g;
	getp(l);
	
	for(int i = 1; i <= cnt; ++i)
	{
		ans *= (long long)a[i];
	}
	ans *= mul(6, cnt);
	
	cout << ans << endl;
	
	return 0;
}


T3 生日礼物

【题面描述】

马上就要是雪菜的生日了,春希摸了摸自己的钱包,发现已经见底。为了给雪菜买生日礼物,春希决定炒股!
知己知彼才能百战百胜,春希通过冬马家的关系知道了股市接下来n天的行情。


在第i天,股票的买入价和卖出价分别为Api和Bpi,并且春希能买入和卖出的股票最多为Asi和Bsi。


由于毕竟是作弊的关系,被发现就一切都完蛋了,冬马叮嘱春希两次交易(买入和卖出都算作一次交易)的间隔必须大于等于w天。也就是说如果第i天交易了,最早也要等到第i+w+1天才能做交易了,并且告诫他,拥有的股票数绝对不能超过Maxp股!


春希最初有无限的钱(冬马借他的,当然是要还的)和零股股票,他想知道,自己在这n天最多能赚多少钱。


【Input】

第一行一个整数t,表示数据组数。


第二行三个整数,分别为n,Maxp,w。(0≤w<n≤2000,1≤Maxp≤2000)
接下来n行,每行4个整数,分别为Api,Bpi,Asi,Bsi。(1≤Bpi≤Api≤1000,1≤Asi,Bsi≤Maxp)


【Output】

一个整数,表示春希能赚的最多的钱。


【样例输入】

1
5 2 0
2 1 1 1
2 1 1 1
3 2 1 1
4 3 1 1
5 4 1 1


【样例输出】

3


题解


没记错的话这道题叫trade 很经典的一道题 = =以前讲过……虽然我没懂 = =
人生中第一道单调队列优化的dp……说起来好惭愧…………以前每次讲都是没听懂然后也没时间去搞懂。。。所以这次写了终于有点感觉了T T
暴力方程很简单 dp[i][j] 表示第i天 拥有j股时的最大钱数
dp[i][j] = max{ dp[i - 1][j], dp[i - w - 1][k] - (j - k) * Ap[i], dp[i - w - 1][k] - (k - j) * Bp[i] }
考试的时候我写的暴力以为能有小数据 = = hhh结果齐爷告诉我们全是大数据没分层出 于是就成功地卡了评测233
由于j - k (或k - j)是有一定范围的 所以我们枚举i更新j的时候 把dp[i-w-1][k]以单调队列维护 
如果队首元素k 丨k - j丨 > 规定范围(Asi 或 Bsi) 那么把队首元素弹出 (保证队列里的元素合法)
如果队尾元素k dp[i - w - 1][k] - (j - k) * Ap[i](或dp[i - w - 1][k] - (k - j) * Bp[i] )比dp[i - w - 1][now]更小了 那么弹出队尾
这样 留在队首的永远是当前最优的k 用队首元素更新dp[i][now]即可

就这么点东西绕了我好久T T

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

const int maxn = 2005;
const int inf = 0x3f3f3f3f;

int T, N, Maxp, w;
int Ap[maxn], Bp[maxn], As[maxn], Bs[maxn];  
int f[maxn][maxn];

struct queue{
	int head, tail;
	int num[maxn], w[maxn];
	
	inline void init(){ head = 0, tail = -1; }
	inline void push_back(int n, int wi){ num[++tail] = n; w[tail] = wi; }
	inline void pop_front(){ ++head; }
	inline void pop_back(){ --tail; }
	inline bool size(){ return tail >= head; }
}q;

int main()
{
	freopen("C.in", "r", stdin);
	freopen("C.out", "w", stdout);
	
	T = read();
	while(T--)
	{
		N = read(); Maxp = read(); w = read();
		
		for(int i = 1; i <= N; ++i) 
		{
			Ap[i] = read(); Bp[i] = read();
			As[i] = read(); Bs[i] = read();
		}
		
		memset(f, -inf, sizeof(f));
		for(int i = 1; i <= w + 1; ++i)
		{
			int lim = min(As[i], Maxp);
			for(int j = 0; j <= lim; ++j)
			{
				f[i][j] = -Ap[i] * j;
			}
		}
		
		for(int i = 2; i <= N; ++i)
		{
			for(int j = 0; j <= Maxp; ++j) f[i][j] = max(f[i - 1][j], f[i][j]);
			if(i <= w + 1) continue;
			
			q.init(); q.push_back(0, f[i - w - 1][0]);
			for(int j = 1; j <= Maxp; ++j) // buy
			{
				while(q.size() && j - q.num[q.head] > As[i]) q.pop_front();
				if(q.size()) f[i][j] = max( f[i][j], q.w[q.head] - Ap[i] * (j - q.num[q.head]) );
				
				while(q.size() && q.w[q.tail] - Ap[i] * (j - q.num[q.tail]) < f[i - w - 1][j]) q.pop_back();
				q.push_back(j, f[i - w - 1][j]);
			}
			
			q.init(); q.push_back(Maxp, f[i - w - 1][Maxp]);
			for(int j = Maxp - 1; j >= 0; --j) // sell
			{
				while(q.size() && q.num[q.head] - j > Bs[i]) q.pop_front();
				if(q.size()) f[i][j] = max(f[i][j], q.w[q.head] + Bp[i] * (q.num[q.head] - j) );
				
				while(q.size() && q.w[q.tail] + Bp[i] * (q.num[q.tail] - j) < f[i - w - 1][j]) q.pop_back();
				q.push_back(j, f[i - w - 1][j]);
			}
		}
		
		printf("%d\n", f[N][0]);
	}
	
	return 0;
}

T4 摩天轮

【题面描述】

一天,冬马被春希和雪菜拉着去一起去游乐园玩。
经过了各种过山车的洗礼后,三人决定去坐摩天轮休息下。


这是一个巨大的摩天轮,每一个车厢能坐任意多的人。现在,等着坐摩天轮的有n个人(包含他们3人),摩天轮还有m个车厢可以坐人。每个人都有自己肥胖程度,出于某些原因,胖子和瘦子坐在同一节车厢就会产生一定的矛盾,这个矛盾的值为(MAX−MIN)2,其中MAX为当前车厢里面最胖的人的肥胖程度,MIN为最廋的那个人的肥胖程度。


爱管闲事的春希当然不希望就因为这点小事而使大家的这趟旅途不愉快,于是他决定帮大家安排怎么坐才能使总的矛盾值最小,希望你能帮他找到这个最小的矛盾值


【输入】

第一行为两个整数n,m,分别表示人数和车厢数。(3≤n≤10000,1≤m≤5000)
第二行为n个整数,wi表示第i个人的肥胖程度。(0≤wi≤1000000)


【Output】

每组数据,输出一个整数,为矛盾的最小值。(答案保证小于2^31)


【样例输入】

4 2
4 7 10 1


【样例输出】

18


题解


第一眼看到这个题觉得是斜率优化 然后写的时候发现好像不太对 然后就想到了MST……(脑洞……不过反正数据范围太大肯定过不了的……)
结果考完发现真的是斜率优化(谁说的第一反应一般都是错的T_T) 排个序然后把人分成连续的块就行了
dp[i][j] = max{ dp[k][j - 1] + a[i]^2 - 2 * a[i] * a[k + 1] + a[k + 1]^2 }
令dp[i][j] - a[i] ^ 2= G, 2 * a[i] = k, a[k + 1] = x, dp[k][j - 1] + a[k + 1]^2 = y
于是等式变为G = -kx + y 即 y = kx + G 求G的最大值
k为定值 根据几何关系得最优的j一定在所有 (x, y) 形成的下凸壳上
所以维护队尾:当k(i, j) <= k(j, k) 时j出队 维护队首:当k(head, head+1) <= nowk时 head出队
不初始化dp[i][1] 会错。。我也不知道为什么 = = 然后我看小明没有初始化不过他写的和我不太一样的样子……他是先更新再计算……

code

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

const int Nmax = 10005;

int n, m;
int w[Nmax];
long long wsq[Nmax]; 
long long dp[Nmax][2]; 

struct queue{
	int num[Nmax];
	int head, tail;
	
	inline void init(){ head = 0; tail = -1; } 
	inline void push_back(int a){ num[++tail] = a; }
	inline void pop_back() { --tail; }
	inline void pop_front() { ++head; }
	inline int size() { return tail - head + 1; }
}q;

inline double get_k(int i, int j, int nw)
{
	if(w[i + 1] == w[j + 1]) return 1e200;
	return 1.0 * (dp[i][nw] + wsq[i + 1] - dp[j][nw] - wsq[j + 1]) / (w[i + 1] - w[j + 1]);
}

int main()
{
	freopen("D.in", "r", stdin);
	freopen("D.out", "w", stdout);
	
	n = read(); m = read();
	for(int i = 1; i <= n; ++i) w[i] = read();
	sort(w + 1, w + n + 1);
	for(int i = 1; i <= n; ++i) wsq[i] = w[i] * w[i];
	
	for(int i = 1; i <= n; ++i) dp[i][1] = wsq[i] + wsq[1] - 2 * w[1] * w[i]; 
	for(int j = 2; j <= m; ++j)
	{
		int nw = j & 1, lst = nw ^ 1;
		
		q.init(); q.push_back(j - 1);
		for(int i = j; i <= n; ++i)
		{
			while(q.size() > 1 && get_k(q.num[q.head], q.num[q.head + 1], lst) <= 2.0 * w[i]) q.pop_front();
			dp[i][nw] = dp[q.num[q.head]][lst] + wsq[i] - 2 * w[q.num[q.head] + 1] * w[i] +  wsq[q.num[q.head] + 1];
			while(q.size() > 1 && get_k(q.num[q.tail-1], q.num[q.tail], lst) >= get_k(q.num[q.tail], i, lst)) q.pop_back();
			q.push_back(i);
		}
	}
	
	cout << dp[n][m & 1] << endl;
	
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值