Xtu2024程设第二次练习题解

1457.图像

在我看来是一个找规律的题目

根据题目的意思,一定是将边长为2^j矩形分解为四个2^(j-2)矩形

而每个矩形内,无论是哪个矩形,都是从上到下,从左到右递增排序的,

变相的我们只要找出每次拆分时,当前点相对于当前大矩形的位置->得到一定的位置偏移量

每次拆分我们就会离最终位置接近一点

更进一步:我们要得到最终位置(行列),其实只要算出每一步分解时的行列增量即可

如果当前分隔时,位于3,4矩形范围内则行偏移要增加一个2^(j-2)(小矩形边长)的长度

同理,2,4矩形范围内则列偏移要增加一个2^(j-2)的长度

对于当前总量k,分隔完之后的剩余总量应该是对小矩形的面积进行取余,即k%=2^(j-2)

对于可分隔的条件,显然是k>2^(j-2)*2^(j-2)[小矩形的面积]

特殊情况,k取余后恰为0,证明它在某次分隔中,恰好分到某小矩形的右下角,这时候显然可以直接人为补充一下偏移量(行列均为2^(j-2)-1)

#include<iostream>
#define ll long long
using namespace std;
ll pre[32];
void solve(ll k) {
	int line = 1,col = 1;
	for (int i = 30; i >= 0; i--) {
		ll v = pre[i];
		ll s = v * v;
		if (!v)v = 1;
		if (s < k) {
			if ( k > 2 * s )line += v;
			if ( k > 3 * s || (k > s && k <= 2 * s))col += v;
			k %= s;
			if(k == 0){
				col += v-1;line += v-1;
			}
		}
	}
	printf("%d %d\n", line, col);
}
int main() {
	pre[0] = 1;
	for (int i = 1; i <= 30; i++)pre[i] = pre[i - 1] * 2;
	int t;
	scanf("%d", &t);
	while (t--) {
		ll k;
		scanf("%I64d", &k);
		solve(k);
	}
	return 0;
}

1550.马里奥

一个很有双指针味道的双指针暴力题(bushi

题目说的还是太隐晦了,简单来说:找到一个区间,使得区间端点之间的距离能大于d,如果找到了就更新ans

用双指针判断查找区间的距离arr[r]-arr[l],每次输入一个arr[i],右指针r++

而左指针仅在arr[r]-arr[l+1]>d时才移动,基于这样一个事实:右指针移动区间范围必定增大,而左指针移动区间必定缩小;和这样一个贪心条件:每次右移完了之后,都控制基于当前右端点,尽可能的缩小左边端点,但同时保证当前区间一定要满足题意(arr[r]-arr[l]>d)

#include<iostream>
using namespace std;
int arr[100010]={0} , w,n,d;
void solve(){
	arr[0] = 1;
	arr[n+1] = w;
	int l = 0,r = 0,ans = n+2;
	for(int i = 1;i<=n+1;i++){
		if(i<=n)scanf("%d",&arr[i]);
		r++;
		while(arr[r]-arr[l+1]>d && l+1<r)l++;
		if(arr[r]-arr[l]>d)ans = min(ans,r-l-1);
	}
	if(d >= arr[n+1]-arr[0]){
		printf("-1\n");
		return;
	}
	printf("%d\n",ans);
}
int main() {
	scanf("%d%d%d",&w,&n,&d);
	solve();
	return 0;
}

1561.制药

一开始想个最基本的办法, 缺什么做什么,然后不断嵌套for循环,然后果不其然超时了

回头仔细想,我们这个思路不行的点就是不知道应该一次性每个药做多少份才好,这个值正好可以二分得到,那么只需写个check验证每次的答案是否正确就行了^_^

#include<iostream>
using namespace std;
int a[1010],b[1010] , n, m;
bool check(int x){
	int all = m;
	for(int i = 1;i<=n;i++){
		int paid = x*a[i]/b[i];
		if(b[i]*paid<x*a[i])paid++;
		all -= paid;
		if(all<0)return false;
	}
	return true;
}
int main() {
	int t;
	scanf("%d",&t);
	while(t--){
		int r = INT_MAX,l = 0;
		scanf("%d%d",&n,&m);
		for(int i = 1;i<=n;i++)scanf("%d",&a[i]);
		for(int i = 1;i<=n;i++){scanf("%d",&b[i]);r = min(r,m*b[i]/a[i]);}
		while(l<=r){
			int mid = (l+r)>>1;
			if(check(mid))l = mid+1;
			else r = mid-1;
		}
		if(check(l))printf("%d\n",l);
		else printf("%d\n",l-1);
	}
	return 0;
}

1577.聚会

思路很简单,直接看代码吧

#include<iostream>
#include<algorithm>
using namespace std;
int a[10100];
int main() {
	int t;
	scanf("%d",&t);
	while(t--){
		//把容易哄来的先弄来
		int n , now = 1;
		scanf("%d",&n);
		for(int i = 0;i<n;i++)scanf("%d",&a[i]);
		sort(a,a+n);
		for(int i = 0;i<n;i++){
			if(now >= a[i])now++;
			else break;
			//容易哄的都来不了,其他更加也来不了
		}
		printf("%d\n",now-1);
	}
	return 0;
}

1584.极大极小堆

其实感觉是谢大出题为了降低难度的同时制造了干扰条件。。。why?

因为最后是要不断取两个极值然后作差放回数列,这样能起到缩小元素的作用从而使两个最大的值变为一个靠近零的值,而当所有元素都是大于等于0的时候,我们其实任何一步中,操作一的优先级都是高于操作二的(当我们为了最终目标——得到一个0,我们不会选择让小的更小,而是让大一点的值尽可能缩小),而为什么会存在删两个极小值的操作呢?(其实原题应该是数列中的元素为-1e8~1e8的范围,那样就需要考虑删极小值的情况,而且会比较复杂)

另外这里我觉得开始插入数据的时候不要直接往优先队列里插,因为插入数据的成本和排序差不多,不如直接sort一次,而后续开始删除操作时就要同时操作数组和优先队列,我觉得这样会比单一使用数组加很多个sort或者单一使用优先队列快一些

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int a[10010] , n, t;
void solve(){
	priority_queue<int>q;
	int p = n-1;
	while(p+1+q.size()>=2){
		int x,y;
		if(!q.empty() && ((p>=0 && q.top()>a[p]) || p<0)){
			x = q.top();q.pop();	
		}else{
			x = a[p--];
		}
		if(!q.empty() && ((p>=0 && q.top()>a[p]) || p<0)){
			y = q.top();q.pop();	
		}else{
			y = a[p--];
		}
		if(x-y)q.push(x-y);
	}
	if(!q.empty())printf("No\n");
	else printf("Yes\n");
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(int i = 0;i<n;i++)scanf("%d",&a[i]);
		sort(a,a+n);
		solve();
	}
	return 0;
}

1433.Swap Digits

这题看题解的bfs+hash感觉远远不够做出这题

不过做出这题还是学到很多东西

  1. std::string在赋值和传参时都会有较大耗时
  2. 判重时set远快于map
  3. 分割边界前要预处理所需要的10的各个次方
#include<iostream>
#include<unordered_set>
using namespace std;
unordered_set<int>mp;
int n, k;
int q[400000], tmp[10], pre[10];
void solve() {
	int hh = 0, tt = 0;
	q[tt++] = n;
	mp.clear();
	mp.insert(n);
	for (int i = 0; i <= k; i++) {
		int end = tt; // 当前队尾
		for (int j = hh; j < end; j++) {
			int now = q[hh++];
			if (i != k) { // 没有交换次数,则不再考虑加入队列
				// 用weight划分交换边界
				int cnt = 0, weight = 1;
				while (now >= pre[weight]) {
					int w = pre[weight];
					// now/weight得到交换分界的前半部分,now%weight得到交换分界的后半部分
					int left = now / w, right = now % w;
					int x = left % 10, y = right / pre[weight - 1];
					left = (left - x + y) * w;
					right = right - y * pre[weight - 1] + x * pre[weight - 1];
					tmp[cnt++] = left + right ;
					weight ++;
				}
				//如果没有前导0,那么一定满足tmp[i] >= weight/10
				for (int i = 0; i < cnt; i++) {
					if (tmp[i] >= pre[weight - 1] && mp.count(tmp[i]) == 0) {
						q[tt++] = tmp[i];
						mp.insert(tmp[i]);
					}
				}
			}
		}
	}
	printf("%d\n", tt);
}
int main() {
	pre[0] = 1;
	for (int i = 1; i <= 9; i++)pre[i] = pre[i - 1] * 10;
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d", &n, &k);
		solve();
	}
	return 0;
}

1532.Fibonacci进制

这题要牢牢抓住题干中的“我们可以把任意一个数x表示成若干不相同的Fibonacci数的和

然后自己推导出一个二级结论:任意数x,斐波那契数列中如果下一项开始大于自己,那么从当前位置往前遍历,一旦遇到比当前x小的,就减去,并统计到结果栈中。

这样得到的一定是能由斐波那契数列构成的最短数列栈(且较小的在栈顶)

那么到这里为止都是由我一开始傻乎乎dfs找出所有可能的fibonacci构造排序说起:我们需要这样一个构造排序,但又不能爆搜得到,那一定存在一种方式能得到一个构造,而较小的构造一定可以由大构造拆分得来,那么就有了我上面说的二级结论(其实到这里就可以猜到了,我后来由暴力验证了1e5以内都是可行的,就大胆做了)

为了得到题目要求的最小表示,这里得说为什么会用栈来存放我们前面得到的构造,因为大构造可以根据斐波那契的性质拆分为前两项,而拆无疑要从小的先拆,根据我们得到数据的先后关系就可以知道,我们后得到较小的,而要先拆较小的,所以这里使用栈来进行存放

#include<iostream>
#include<stack>
#define ll long long
using namespace std;
ll ans , x , p[50],fibo[50]; //43个(第44个算出来作为边界)1e9以内的斐波那契,所以2也要算出42次方
stack<int>st;
void solve(){
	int vis[50]={0};
	int loc = 0;
	while(x>=fibo[loc+1])loc++;
	while(x){
		if(x >= fibo[loc]){
			x -= fibo[loc];
			st.push(loc);
			vis[loc] = 1;
		}
		loc--;
	}
	while(!st.empty()){
		int top = st.top();st.pop();
		if(top >= 2 && !vis[top-1] && !vis[top-2]){
			vis[top] = 0;
			vis[top-1] = vis[top-2] = 1;
			st.push(top-1);st.push(top-2);
		}else x += p[top];
	}
	printf("%I64d\n",x);
}
int main(){
	fibo[0] = 1;fibo[1] = 2;p[0] = 1;p[1] = 2;
	for(int i = 2;i<=44;i++){
        fibo[i] = fibo[i-1]+fibo[i-2];
        p[i] = p[i-1]*2;
        printf("%d--%d\n",i,fibo[i]);
    }
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%I64d",&x);
		solve();
	}
//	for(int i = 1;i<=1000000;i++){
//		printf("%d----",i);
//		x=i;solve();
//	}
}

1534.Black White Chess

将一次染色操作看成一次二进制数的异或操作,所以每一次操作都可以用一个二进制数替代,进而我们只需要预先算出对应的10进制数存储到op中即可。

同时对于一个起始值能到达的所有可能情况都标记为同一个值,就相当于对65536以内的每个值[0,65536)进行bfs,如果该点的连通分量为0(证明他还没被之前任何值生成过,那么它可以作为一个新的连通分支,来跑一个bfs)

#include<iostream>
#include<queue>
using namespace std;
int op[20] = {15, 240, 3840, 61440, 4369, 8738, 17476, 34952, 51, 102, 204, 816, 1632, 3264, 13056, 26112, 52224};
char s1[20], s2[20];
int fa[65536] = {0};
void init() {
	int cnt = 1;
	queue<int>q;
	q.push(0);
	for (int u = 0; u < 65536; u++) {
		if (fa[u] == 0) {
			q.push(u);
			fa[u] = cnt;
			while (!q.empty()) {
				int x = q.front();
				q.pop();
				for (int i = 0; i < 17; i++) {
					int var = x ^ op[i];
					if (fa[var] == 0)fa[var] = cnt, q.push(var);
				}
			}
			cnt++;
		}
	}
}
int main() {
	init();
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%s%s", s1, s2);
		int a = 0, b = 0;
		for (int i = 15; i >= 0; i--) {
			a = (a << 1) + s1[i] - '0';
			b = (b << 1) + s2[i] - '0';
		}
		if (fa[a]==fa[b])printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

1545.幂

emm.....一言难尽.......又抽象又优雅的dfs

#include<iostream>
#define ll long long
using namespace std;
char s[110];
int p;
ll dfs(){
	ll res = 0;
	while(s[p]!='\0'){
		if(s[p] == '('){
			p++;
			res += dfs();
			//全局指针p会移动到其对应右括号位置
			if(s[p+1]!='(')res *= res;
//遇到右括号就return
		}else if(s[p] == ')')return res;
		else res = s[p] - '0';
		p++;
	}
	return res;
}
int main(){
	while(scanf("%s",s)!=EOF){
		p = 0;
		printf("%I64d\n",dfs());
	}
}

1568.完全二叉排序树

这题完全不需要建树,只需要用顺序存储的方式,当存储位置大于n时,那么就一定不满足完全二叉树的条件,而存储规则则根据二叉排序树来进行即可

#include<iostream>
#include<memory.h>
using namespace std;
int tree[1100];
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		memset(tree, 0, sizeof tree);
		int n;
		scanf("%d", &n);
		bool isFair = true;
		for (int i = 1; i <= n; i++) {
			int now = 1, x;
			scanf("%d", &x);
			if (isFair) {
				while (tree[now]) {
					if (x > tree[now])now = 2 * now + 1;
					else now = 2 * now;
				}
				tree[now] = x;
				if (now > n)isFair = false;
			}
		}
		if(isFair)printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

1585.数字

看到题解中的bfs典型题莫名难受,看到这种题总有种暴力会过不了的错觉。。。

我也是很无脑的写了一坨shit。。。

#include<iostream>
#include<queue>
#include<memory.h>
using namespace std;
const int maxn = 1e6;
bool vis[maxn+10];
int ans,x ,y ;
void solve(){
    queue<int>q;
    q.push(x);
    int cnt = 0;
    while(!q.empty()){
        int size = q.size();
        for(int i = 1;i<=size;i++){
            int now = q.front();q.pop();
            if(now == y){ans = cnt;return ;}
            if(now-1>0 && !vis[now-1]){q.push(now-1);vis[now-1]=1;}
            if(now+1<maxn && !vis[now+1]){q.push(now+1);vis[now+1]=1;}
            if(now/2>0 && !vis[now/2]){q.push(now/2);vis[now/2]=1;}
            if(now*2<maxn && !vis[now*2]){q.push(now*2);vis[now*2]=1;}
        }
        cnt ++;
    }
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        memset(vis,0,sizeof vis);
        scanf("%d%d",&x,&y);
        solve();
        printf("%d\n",ans);
    }
    return 0;
}

1586.积

对贪心和取模留下阴影了。。。

首先直接假设结果为正,将数组从小到大排好序

(如果k为奇数,那么必有一个单独取的,这个单独取的必须是一个非负数,不然肯定凑不出正数,然后尽量保证结果最大,因为我们已经假设结果是正,所以直接取一个最大的非负数,那么k就变为偶数了)

然后头尾各取两个,谁大取谁(如果过程中,选出的值产生了负,那么结果就必然为负,执行负数策略)

判定结果为负时,直接按绝对值从小到大取k个即为结果,可排序可双指针(我交了好像耗时没区别)

取模的细节:头尾各取两个比较大小的时候不能取模,因为这关乎选择结果。在乘到结果ans中的时候,需要取模,因为我们能保证两个在mod范围内的数乘起来不会爆LL,但是不能保证三个在mod范围内的数乘起来不会爆LL,也就是需要写成ans = ans * (a % mod) % mod

#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int mod = 1e9 + 7;
ll arr[10010], res, a, b;
int n, k, y;
void count() {
	res = 1;
	int p = y - 1, q = y;
	while (k) {
		ll a = 2e9, b = 2e9;
		if (p >= 0)a = abs(arr[p]);
		if (q < n)b = abs(arr[q]);
		if (a < b)res = res * arr[p--] % mod;
		else res = res * arr[q++] % mod;
		k--;
	}
	printf("%I64d\n", res);
}
ll solve() {
	ll ans = 1;
	int s = 0, e = n - 1, cnt = k;
	if (cnt & 1)ans = ans * arr[e--] % mod, cnt--;
	while (cnt) {
		a = arr[s] * arr[s + 1], b = arr[e] * arr[e - 1];
		if (a > b)s += 2, ans = ans * (a % mod) % mod;
		else e -= 2, ans = ans * (b % mod) % mod;
		cnt -= 2;
		if (ans == 0)return 0;
		if (ans < 0)return -1;
	}
	return ans;
}
/*
这是用sort来写的按绝对值取值
bool cmp(int a, int b) {
    return abs(a) < abs(b);
}
void count() {
    res = 1;
    sort(arr, arr + n, cmp);
    for (int i = 0; i < k; i++)res = res * arr[i] % mod;
    printf("%I64d\n", res);
}
*/
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d", &n, &k);
		y = 0;
		for (int i = 0; i < n; i++) {
			scanf("%I64d", &arr[i]);
			if (arr[i] < 0)y++;
		}
		sort(arr, arr + n);
		res = solve();
		if (res >= 0)printf("%I64d\n", res);
		else count();
	}
	return 0;
}

1587.异或树

这道题想通了很简单,就是如果一个节点,不管它子节点啥样,只要它和父节点不同,那就必定要取反一次,也就是结果ans++,那么直接遍历整棵树即可

#include<iostream>
#include<vector>
using namespace std;
vector<vector<int>>v;
int val[10010],ans;
void dfs(int x,int fa){
	if(val[x]!=val[fa])ans ++ ;
	for(auto i :v[x])if(i!=fa)dfs(i,x);
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		ans = 0;
		int n;
		scanf("%d",&n);
		vector<vector<int>>(n+1).swap(v);
		for(int i = 1;i<n;i++){
			int u,w;
			scanf("%d%d",&u,&w);
			v[u].push_back(w);
			v[w].push_back(u);
		}
		for(int i = 1;i<=n;i++)scanf("%d",&val[i]);
		dfs(1,1);
		printf("%d\n",ans);
	}
}

1448.按位与

思路,按位与出一个非0的结果,根据2进制可以知道,相当于是这些数在2进制上某一位全为1,所以要转化为每个数对一个二进制位的判断,比如2(10)在二进制的第一位为0,第二位为1,这也就是谢大题解中说的01串的由来,对于我们检查的某一个二进制位,就是一个典型的双指针问题。

要得到一个数x在2进制中i位上是否为0,只要用x&pre[i],这个式子的结果只有两个,0或者pre[i] 

预处理2的n次方,提前跑一下知道n=29,2的30次方已经超过1e9,所以是29

#include<iostream>
using namespace std;
int pre[32] = {0}, var[10010], n, k, ans;
void check(int q) {
	int l = 0, r = 0, cnt = 0;
	while (r < n) {
		while (r < n && cnt <= k) {
			if ((pre[q] & var[r]) == 0)cnt++;
			r++;
		}

        // 这里为什么要分类可以自己思考一下
		if (cnt > k)ans = max(r - l - 1, ans);
		else ans = max(r - l, ans);
		while (l < r && cnt > k) {
			if ((pre[q] & var[l]) == 0)cnt--;
			l++;
		}
	}
}
int main() {
	int t;
	pre[0] = 1;
	for (int i = 1; i <= 30; i++)pre[i] = pre[i - 1] * 2;
	scanf("%d", &t);
	while (t--) {
		ans = 0;
		int maxn = 0;
		scanf("%d%d", &n, &k);
		for (int i = 0; i < n; i++) {
			scanf("%d", &var[i]);
			maxn = max(var[i], maxn);
            // 用maxn来确定我们枚举2进制位数的上限
		}
		for (int i = 0; i <= 29; i++) {
			if (maxn < pre[i])break;
			check(i);
		}
		printf("%d\n", ans);
	}
}

1460.距离

1555.数组

对于题意理解:任取两个数使得它们的和能在[L,R]上

通过二分锁定区间,得到哪些值一定不可能有与之配对的值使和在[L,R]上

比如给定[L,R],一个数xo加上最大的arr[n-1]还小于L,那必不可能有,或者一个数xo加上最小的arr[0]还大于R,那也必不可能有。(这就是区间上下界的由来,区间最小的值一定是一个大于等于L-arr[n-1]的值,区间最大的值一定是一个小于等于R-arr[0]的值)

(查找区间的表达式[ 不小于L-arr[n-1],不大于R-arr[0] ])

关于两个二分可以参考AcWing博客

这里双指针比较巧妙的点在于,对一个大数而言,它只可能与较小数相加在[0,x]区间内,当较小数将它抛弃后(右指针左移),那对于当前左指针右边的所有数,都不可能与这个大数相加在[0,x]内

#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int arr[100010], n, m, L, R;
long long ans;
// 找不小于aim的第一个数的位置
ll mid_find1(int aim) {
	int l = 0, r = n - 1;
	while (l < r) {
		int mid = (l + r) >> 1;
		if (arr[mid] < aim)l = mid + 1;
		else r = mid;
	}
	return l;
}
// 找不大于aim的最后一个数的位置
ll mid_find2(int aim) {
	int l = 0, r = n;
	while (l + 1< r) {
		int mid = (l + r) >> 1;
		if (arr[mid] < aim)l = mid;
		else r = mid;
	}
	return l;
}
ll count(ll l, ll r, int x) {
	ll res = 0;
	while (l <= r) {
		if (arr[l] * 2 > x) {
			l++;
			continue;
		}
		while (arr[r] + arr[l] > x && r - 1 >= l)r--;
		res += r - l + 1ll;
//		printf("%d对%d贡献:%d\n",l,x,tmpr-l+1);
		l++;
	}
	return res;
}
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d%d%d", &n, &arr[0], &arr[1], &m);
		scanf("%d%d", &L, &R);
		ans = 0;
		for (int i = 2; i < n; i++)arr[i] = (arr[i - 1] + arr[i - 2]) % m;
		sort(arr, arr + n);
//		for (int i = 0; i < n; i++)printf("%d ", arr[i]);
//		printf("\n");
		// 结果区间为[L,R]
		// 则a+b的区间将b看作参数,a的区间应该为[L-max(b),R-min(b)]
		if (arr[0] * 2 > R || arr[n - 1] * 2 < L) {
			printf("0\n");
			continue;
		}
		ll l = mid_find1(L - arr[n - 1]), r = mid_find2(R - arr[0]);
//		printf("[%d,%d]\n", l, r);
		ans = count(l, r, R) - count(l, r, L - 1);
		printf("%I64d\n", ans);
	}
}

1566.删除

1570.斐波拉契数的平方和

也是第一次学矩阵快速幂,看了题解和一些教程后才过的这题

矩阵快速幂重点:找到递推关系,找到关系矩阵(一般为方阵),不是方阵的话不能进行快速幂的优化

1.快速幂入门教程        2.快速幂改装版教程

这两个教程的做法其实都可以在仅调用两次求矩阵快速幂函数下求出结果,关键在于对于初始矩阵以及结果矩阵中值的理解,以及关系矩阵的写法。

(这两个教程中的斐波那契下标都从1开始,而我们题目中都为0开始,而这两个教程的关系矩阵也有出入,导致了结果矩阵意义的不同,如果两个都能看懂并转化成此题的写法的话,应该也是懂得差不多了)

关于过程中会取模,结果中又做减法,一定不要直接res%mod,要写成(res+mod)%mod

#include<iostream>
#include<cstring>
#define ll long long
using namespace std;
const ll mod = 1e9 + 7;
void mult(ll a[][2], ll b[][2]) {
	ll re[2][2] = {0};
	for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) re[i][j] = (re[i][j] + (a[i][k] * b[k][j]) % mod ) % mod;
	memcpy(a, re, sizeof re);
}
ll solve(int x) {
	if (x < 0)return 0;
	ll res[2][2] = { 1, 1, 0, 0};
	ll A[2][2] = {1, 1, 1, 0};
	while (x) {
		if (x & 1)mult(res, A);
		mult(A, A);
		x >>= 1;
	}
	return res[0][0] * res[0][1] % mod;
}
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		int a, b;
		scanf("%d%d", &a, &b);
		printf("%I64d\n", (solve(b) - solve(a - 1) + mod) % mod);
	}
}

1581.斐波那契+1

这题能不能做出来还是看上一题有没有理解矩阵的意义

因为我们要用gn+1,gn推出gn+2,gn+1涉及到一个常数项1,

(g_{n+2},g_{n+1}) = (g_{n+1}+g_{n}+1,g_{n+1})

而一个1*2和一个2*2的关系矩阵无论如何凑不出那个1,这时候递推的参数位显然需要补一个1,那么就是如下递推关系式

(g_{n+2},g_{n+1},1) = (g_{n+1}+g_{n}+1,g_{n+1},1)

由于矩阵乘法的条件,或者说增加参数位的原因,2*2的方阵肯定得扩为3*3的方阵,那么只需根据乘法原则手推一下表达式即可

\begin{pmatrix} g_{n+2} & g_{n+1} &1 \\ 0&0 &0 \\ 0&0 &0 \end{pmatrix} = \begin{pmatrix} g_{n+1} &g_{n} &1 \\ 0 &0 &0 \\ 0 &0 &0 \end{pmatrix} *\begin{pmatrix} 1& 1 & 0\\ 1& 0& 0\\ 1& 0 &1 \end{pmatrix}

所以关系矩阵就是最右边的那个,而初始化结果矩阵就是将g0=g1=1代入左边

剩下的和上一题的矩阵快速幂大同小异

#include<iostream>
#include<string.h>
#define ll long long
using namespace std;
ll mod = 1e9+7;
void mult(ll a[][3],ll b[][3]){
	ll tmp[3][3] = {0};
	for(int i = 0;i<3;i++)for(int j = 0;j<3;j++)for(int k = 0;k<3;k++)tmp[i][j] = (tmp[i][j] + a[i][k] * b[k][j] ) % mod;
	memcpy(a,tmp,sizeof tmp);
}
ll solve(int n){
	ll res[3][3] = {1,1,1};
	ll A[3][3] = {1,1,0,1,0,0,1,0,1};
	n--;
	while(n>0){
		if(n&1)mult(res,A);
		mult(A,A);
		n>>=1;
	}
	return res[0][0];
}
int main(){
	int t,n;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		printf("%I64d\n",solve(n));
	}
}

1390.字母计数

这题我感觉就是幂(1545)那题改装了一下,对于每一层需要处理的数据更多,不是一个简单的变量就能存储的,其他的用一个套路就能做出来

#include<iostream>
#include<string.h>
#include<vector>
using namespace std;
char str[60];
int p, len;
vector<int>cnt(26);
bool check(int v) {
	if (str[v] >= '0' && str[v] <= '9')return true;
	return false;
}
vector<int> dfs() {
	vector<int>tmp(26, 0);
	vector<int>get;
	while (p < len) {
		if (str[p] == '(') {
			p++;
			get = dfs();
			for (int i = 0; i < 26; i++)tmp[i] += get[i];
		} else if (str[p] == ')') {
			int x = 0;
			while (check(p + 1))x = x * 10 + str[++p] - '0';
			for (int i = 0; i < 26; i++)tmp[i] *= x;
			return tmp;
		} else {
			int cnt = 0;
			if (check(p+1)) {
				char var = str[p];
				while(check(p+1))cnt = cnt * 10 + str[++p] - '0';
				tmp[var - 'a'] += cnt;
			} else {
				tmp[str[p]-'a']++;
			}
		}
		p++;
	}
	return tmp;
}
int main() {
	while (scanf("%s", str) != EOF) {
		p = 0;
		len = strlen(str);
		cnt = dfs();
		for (int i = 0; i < 26; i++)if (cnt[i])printf("%c : %d\n", 'a' + i, cnt[i]);
		printf("\n");
	}
}

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值