又是新的开始,拒绝摆烂,从我做起
第一题
“水题”,我还差点不会了。经过一番思考后我终于明白,先用点的横纵坐标和0以及n+1求差取绝对值,取每一对绝对值的最小值,然后再这两者中取最小值,就能知道在“第几圈”了。
t = min(min(i, n+1-i), min(j, n+1-j));
AC code:
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
int n;
cin >> n;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= n; ++j)
{
int t = min(min(i, n+1-i), min(j, n+1-j));
if(t%2==1)
{
cout << '+';
}
else
{
cout << '.';
}
}
cout << endl;
}
return 0;
}
第二题
梅开二度,依然是“水题”,但是我是真真正正的不会了,呜呜呜,
AC code上,思路就是用一个二维数组来存储当前层数有多少种情况,其中第一维度表示上了多少级台阶,第二维度表示最近三次中有多少次连续的2步(当且仅当最后一次是两步时才算数,最后一次是一步的话那么按0算)。然后根据递推关系不难写出状态方程。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int f[52][3] = {0};
int n;
signed main()
{
cin >> n;
f[1][0] = f[2][0] = f[2][1] = 1;
for(int i = 3; i <= n; ++i)
{
f[i][0] += f[i-1][0]+f[i-1][1]+f[i-1][2];
f[i][1] += f[i-2][0];
f[i][2] += f[i-2][1];
}
cout << f[n][0]+f[n][1]+f[n][2];
return 0;
}
之前还有按照我自己想法来写的代码,WA代码示例(别看)
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
int f[52];
f[0] = f[1] = 1;
int n;
cin >> n;
for(int i = 2; i <= n; ++i)
{
f[i] = f[i-1]+f[i-2];
if(i >= 6)
{
f[i] -= f[i-6];
}
}
cout << f[n] << endl;
return 0;
}
接下来分析一下这个代码为什么会WA,输入测试用例8,9可以发现代码运行结果比正确答案要少很多,很明显是减重了。为什么是减多了?f[i] -= f[i-6]本意是减去连着三次两步的情况,但是f[i-6]中可能会蕴含最后一次是走两步的情况,这样也就意味着减掉了连续走四次两步的情况。也就意味着f[i-2]中已经有连续三步的情况了,但是f[i-2]中我们已经减掉了连续三次两步的情况。这就与假设矛盾。所以这种做法不可取。综上考量,我们也只能采用二维数组了。(当然也可能有优化方法)
第三题
我还不至于不会走路,这题和上题一样,只需要用二维数组处理dp的情况就可以了,详细情况看代码注解即可。
#include <bits/stdc++.h>
using namespace std;
//说白了这题还是dp
int n, m;
const int MAXM = 1E5+5, MAXN = 102;
bool dp[MAXM][MAXN]; //dp,第一个维度是步长,第二个维度是次数
int main()
{
dp[0][0] = 1;
cin >> n >> m;
int a, b;
for(int i = 1; i <= n; ++i)
{
cin >> a >> b;
for(int j = 0; j <= m; ++j)
{
if(j >= a)
{
dp[j][i] = dp[j-a][i-1] || dp[j][i];
}
if(j >= b)
{
dp[j][i] = dp[j-b][i-1] || dp[j][i];
}
}
}
for(int i = 0; i <= m; ++i)
{
cout << dp[i][n];
}
return 0;
}
第四题
这题只需要建立题目和姓名之间的映射就可以了。比较容易实现,是真的水题
#include <bits/stdc++.h>
using namespace std;
int n, m, k;
string names[202], pros[202], status[202];
int scores[202], _scores[202];
int main()
{
cin >> n >> m >> k;
for(int i = 1; i <= n; ++i)
{
string name;
cin >> name;
names[i] = name;
}
for(int i = 1; i <= m; ++i)
{
string pro;
int score;
cin >> pro >> score;
pros[i] = pro;
scores[i] = score;
}
for(int i = 1; i <= k; ++i)
{
string name, pro, status;
int j, t;
cin >> name >> pro >> status;
for(j = 1; j <= n; ++j)
{
if(names[j] == name)
{
break;
}
}
for(t = 1; t <= m; ++t)
{
if(pros[t] == pro)
{
break;
}
}
if(status == "AC")
{
_scores[j] += scores[t];
}
}
for(int i = 1; i <= n; ++i)
{
cout << names[i] <<' '<< _scores[i] << endl;
}
return 0;
}
第五题
不会玩牌的我看的一脸懵,这题不难就是纯属恶心人,直接暴力求解吧。
#include <bits/stdc++.h>
using namespace std;
struct pai
{
int num;
int col;
} p[6];
int check()
{
if(p[1].num == p[2].num-1 && p[1].num == p[3].num-2 && p[1].num == p[4].num-3
&& p[1].num == p[5].num-4 && p[1].col == p[2].col && p[1].col == p[3].col
&& p[1].col == p[4].col && p[1].col == p[5].col)
{
if(p[5].num == 14)
{
return 1;
}
else
{
return 2;
}
}
else if(p[1].num == p[2].num && p[1].num == p[3].num && p[1].num == p[4].num
|| p[2].num == p[3].num && p[2].num == p[4].num && p[2].num == p[5].num)
{
return 3;
}
else if(p[1].num == p[2].num && p[3].num == p[4].num && p[3].num == p[5].num
|| p[1].num == p[2].num && p[1].num == p[3].num && p[4].num == p[5].num)
{
return 4;
}
else if(p[1].col == p[2].col && p[1].col == p[3].col && p[1].col == p[4].col
&& p[1].col == p[5].col)
{
return 5;
}
else if(p[1].num == p[2].num-1 && p[1].num == p[3].num-2 && p[1].num == p[4].num-3
&& p[1].num == p[5].num-4)
{
return 6;
}
else
{
return 7;
}
}
int main()
{
for(int i = 1; i <= 5; ++i)
{
cin >> p[i].num;
}
for(int i = 1; i <= 5; ++i)
{
cin >> p[i].col;
}
switch(check())
{
case 1:
cout << "ROYAL FLUSH";
break;
case 2:
cout << "STRAIGHT FLUSH";
break;
case 3:
cout << "FOUR OF A KIND";
break;
case 4:
cout << "FULL HOUSE";
break;
case 5:
cout << "FLUSH";
break;
case 6:
cout << "STRAIGHT";
break;
case 7:
cout << "FOLD";
break;
}
cout << endl;
return 0;
}
第六题
本来打算用暴力求解的办法,用map存储每个数据是否被引用过,如果数据已经被引用过就+1,直到找到未被引用的数据。数据的规模决定了暴力求解是必定超时的。
看了一种题解,之前没用过set,算是学习了。lower_bound是找到>=指定数的最小元素,upper_bound是找到>指定元素的最小元素。由于数据规模比较大,所以必须要用二分,才能不会超时。我们就用set来存储所有区间,每插入一个数就将区间划分,如果插入的数已经被引用,那么就找到他后面区间的开始点,再划掉开始点。由于需要按照区间的结束点来查找(这样才能保证查找到的指定点可能在区间内,要是按照区间的开始点做第一个元素存储就只能找小于指定区间的最大元素了,没有相应的函数)所以区间的结束点作为pair的第一个元素。
还有这题会卡输入输出的时间。如果用cin输入会超时,用scanf就不会。
#include <bits/stdc++.h>
using namespace std;
set<pair<int, int> > qu;
int n;
inline void minsert(int l, int r)
{
if(l > r)
{
return;
}
else
{
qu.insert(make_pair(r, l));
}
}
int main()
{
minsert(1,2E9);
scanf("%d", &n);
int x;
for(int i = 1; i <= n; ++i)
{
scanf("%d", &x);
auto it = qu.lower_bound(make_pair(x, 0));
int l = it->second, r = it->first;
qu.erase(it);
if(l <= x)
{
printf("%d ", x);
minsert(l, x-1);
minsert(x+1, r);
}
else
{
printf("%d ", l);
minsert(l+1, r);
}
}
return 0;
}
快速输入的板子,如果要快速输出,那么cout.tie(NULL);
ios_base::sync_with_stdio(false);
cin.tie(NULL);
第七题
首先想到的就是用一个队列来暴力模拟,本来想着O(k)怎么也超不了时,谁知道这个数据规模是1E14,看来铁定不能用暴力模拟了,但是还有什么办法呢?
和上一题一样,还是二分答案。这种数据规模贼大的多半就要用二分答案。可是有思路我也不会写啊?那就只能二分找到总轮数,然后就对完整的轮数走完剩余的情况进行模拟就可以了。怎么知道轮数呢?只能二分一个一个试了。有的已经喂饱了,就不能算入在内了。 需要对min(arr[i],轮数)求和。模拟的话肯定是用队列模拟了。
#include<bits/stdc++.h>
#define int long long
using namespace std; //显然的二分答案题目,但是还是不太行
int n, k, sum;
const int maxn = 1E5+2;
int arr[maxn];
//t为实际的总轮数,要寻找总数<=k的最大轮数
bool check(int t)
{
int tmp = 0;
for(int i = 1; i <= n; ++i)
{
tmp += min(arr[i], t);
}
return tmp <= k;
}
signed main(){
cin >> n >> k;
for(int i = 1; i <= n; ++i)
{
cin >> arr[i];
sum += arr[i];
}
if(sum < k)
{
cout << -1;
return 0;
}
int l = 0, r = 1E9, mid = l+(r-l)/2;
int ans = 0; //最后测试的数据不一定是答案,要额外存储
while(l <= r)
{
mid = l+(r-l)/2;
if(check(mid))
{
ans = max(mid, ans);
l = mid+1;
}
else
{
r = mid-1;
}
}
sum = 0; //重用sum变量
queue<int> q;
for(int i = 1; i <= n; i++)
{
sum += min(ans, arr[i]);
arr[i] -= min(ans, arr[i]);
if(arr[i])
{
q.push(i);
}
}
int s = k-sum; //这个地方已经是最后一轮了,可以模拟了
for(int i = 1; i <= s; i++)
{
int t = q.front();
q.pop();
arr[t]--;
if(arr[t]>0)
{
q.push(t);
}
}
while(!q.empty())
{
cout << q.front() << ' ';
q.pop();
}
}
第八题
一看就是一个动规的题,不过和背包问题有相异之处。因为在这个活动开始之前结束的活动都可以和这个活动一起安排。我最初打算第一层循环遍历数组,但是这样对于第二维的处理就会陷入难题。如果换个思路,第一维循环结束时间,这样或许就比较容易一些了。上个活动结束和本活动开始时间可能会有间隔,所以dp[i]=dp[i-1]就是为了解决这个问题。第一轮循环正向遍历就能保证每个时间的活动都会被考虑并加入总和。if(i >= e[i]) dp[i] = max(dp[i], dp[s[j]]+w[j])这个就是加入活动的情况。
再次重申dp[i]表示总结束时间在i及以前的最优情况。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1E3+2;
int n;
int s[maxn], e[maxn], w[maxn];
int dp[maxn]; //动态规划题目,比较难理解
int main()
{
cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> s[i] >> e[i] >> w[i];
}
for(int i = 1; i <= 1000; ++i)
{
dp[i] = dp[i-1];
for(int j = 1; j <= n; ++j)
{
if(i >= e[j])
{
dp[i] = max(dp[i], dp[s[j]]+w[j]);
}
}
}
cout << dp[1000] << endl;
return 0;
}
第九题
本来以为这题真简单,随手一写bfs,通过了测试就去提交,结果迎来光速吃席打脸!!!真是天黑路滑,社会复杂
光看结果可能比1E9大就知道肯定会超时。其实dp本来就是dfs的升级版,我们最初背包问题的dp就是为了解决dfs的O2^n超时问题,所以我们可以使用dp来求解这个问题(记忆化搜索,可以避免重复查找),最多1E6次循环,不会超时,递推方程还是容易的。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1E9+7;
int n;
bool arr[102][102];
int dp[102][102];
//起始点的位置默认为1,1
signed main()
{
cin >> n;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= n; ++j)
{
cin >> arr[i][j];
}
}
dp[1][1] = 1;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= n; ++j)
{
if(arr[i][j])
dp[i][j] = max(dp[i][j]%mod, (dp[i-1][j]+dp[i][j-1])%mod);
else
dp[i][j] = 0;
}
}
cout << dp[n][n] << endl;
return 0;
}
第十题
依旧是动规问题。但是数组存储的元素应该是什么呢?经过思考,觉得dp[i]表示以i为结尾的子序列最大值再好不过了。这样就只用0到i-1来查询就可以了。还要注意一点的就是, 二重循环的内层一定要从0开始循环,因为a[i]可能是子序列的开始点,知道这些那么就是轻轻松松简简单单了。AC code:
#include <bits/stdc++.h>
using namespace std;
int n, ans;
int a[1002], dp[1002]; //最大是1E8,不会超
//dp[i]的意思是,以i为末尾的子序列的最大值
int main()
{
cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> a[i];
}
dp[0] = 0; //默认a[0] = 0
for(int i = 1; i <= n; ++i)
{
for(int j = 0; j < i; ++j)
{
if(a[i] > a[j])
{
dp[i] = max(dp[i], dp[j]+a[i]);
ans = max(dp[i], ans);
}
}
}
cout << ans << endl;
return 0;
}