一、九进制转十进制
题目

个人见解
手算或者进制转换都可。
AC代码
cpp
#include <iostream>
using namespace std;
int main()
{
cout<<1478;
return 0;
}
二、顺子日期
题目

个人见解
直接挨个数即可,但是需要注意这里的 "顺子日期" 自带升序性质。
AC代码
cpp
#include <iostream>
using namespace std;
int main()
{
cout<<14;
return 0;
}
//0120
//0121
//0122
//0123
//0124
//0125
//0126
//0127
//0128
//0129
//1012
//1123
//1230
//1231
三、刷题统计
题目

个人见解
这里还是需要先用目标题数除以一个星期的题数剪枝一下,不然只能过 60% 的数据。
AC代码
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
ll a,b,n;
cin>>a>>b>>n;
ll num=0;
ll c=0;
ll ans=n/(5*a+2*b)*7;
n=n%(5*a+2*b);
while(num<n)
{
if(c<=4)num+=a;
else num+=b;
c++;
ans++;
}
cout<<ans;
return 0;
}
四、修剪灌木
题目


个人见解
比较好写的思维题。
AC代码
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
ll N;
cin>>N;
for(ll i=1;i<=N;i++)
{
ll maxv=max(i-1,N-i);
cout<<2*maxv<<endl;
}
return 0;
}
*五、X进制减法
题目


个人见解
本题不好理解的点在于题目给出的样例,即 X 进制数 321 是如何转换成十进制数 65 的。
其实题目描述的 X 进制只有在进位的时候有作用,某数位上的 1 代表的十进制取决于上一数位的 1 。
例如:最低位数为二进制,那么最低位数的 1 代表 1 ,当该位数的数字达到 2 就会进位;第二位数为十进制,但是是由最低位数进位来的,因此该位数的 1 代表 2 ,当该位数的数字达到 10 就会进位;第三位数为八进制,同理,该位数的 1 代表 20 ,当该位数达到 8 就会进位。
因此十进制数 65 是由 3 × 20 + 2 × 2 + 1 × 1 = 65 3\times20+2\times2+1\times1=65 3×20+2×2+1×1=65 得来的。
本题的贪心很好想,就是每个位数尽量取最小的进制。
接下来的普通解法就很容易想到了,直接借位即可,但是借位真的很麻烦。
这里推荐另一种解法---贡献法,因为只要求出最后的十进制数字,所以根本不需要关注数组元素相减可能出现负数的情况,只需要无脑算出每位的贡献即可。
最后!不要忘记输出的时候加上 mod ,否则可能会输出负数,导致不能 ac 。
cpp
cout<<(ans+mod)%mod;
AC代码(贡献法)
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll mod=1e9+7;
ll a[100005];
ll b[100005];
ll ans=0;
int main()
{
ll N;
cin>>N;
ll ma,mb;
cin>>ma;
for(ll i=1;i<=ma;i++)
{
cin>>a[i];
}
cin>>mb;
for(ll i=1;i<=mb;i++)
{
cin>>b[i];
}
ll prev=1;
while(mb)
{
ll w=max(a[ma],b[mb])+1; //当前位数的进制
w=max(w,2LL);
ll cur=(a[ma]-b[mb])*prev%mod; //当前数位的贡献
prev=w*prev%mod;
ans=(ans+cur)%mod;
mb--,ma--;
}
while(ma)
{
ll w=max(2LL,a[ma]+1);
ll cur=a[ma]*prev%mod;
prev=w*prev%mod;
ans=(ans+cur)%mod;
ma--;
}
cout<<(ans+mod)%mod;
return 0;
}
*六、统计子矩阵
题目


个人见解
标准的滑动窗口题,这类题写的还是太少了,二维前缀和转成一维前缀和想了很久很久...
下面分别提供以列为前缀和&以行为前缀和两种出发点,但是本质都是一样的。
AC代码(滑动窗口)
- 将二维数组压缩成列前缀和数组。
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll n, m, k;
ll a[505][505];
ll s[505][505]; // s[i][j] 表示第 j 列、前 i 行的垂直累加和
ll ans = 0;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>k;
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=m;j++)
{
cin>>a[i][j];
// 计算列前缀和:当前格 + 同一列上一格的和
s[i][j]=s[i-1][j]+a[i][j];
}
}
for(ll i=1;i<=n;i++) // 1. 固定上边界 i (从第几行开始)
{
for(ll j=i;j<=n;j++) // 2. 固定下边界 j (到第几行结束)
{
// 3. 此时高度固定了,开始水平方向的滑动窗口
ll L=1;
ll sum=0; // 当前左右边界 [L, R] 之间的子矩阵总和
for(ll R=1;R<=m;R++)
{
sum+=(s[j][R]-s[i-1][R]); // 加上第 R 列在 i~j 行之间的垂直片段
// 如果窗口超出范围了,左边往右缩
while(sum>k&&L<=R)
{
sum-=(s[j][L]-s[i-1][L]);
L++;
}
// 计数逻辑:
// 如果 [L, R] 合法,那么以 R 为右边界的子矩阵有:
// [L, R], [L+1, R], ..., [R, R],共 R-L+1 个
ans+=(R-L+1);
}
}
}
cout<<ans<<endl;
return 0;
}
2.将二维数组压缩成行前缀和数组。
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m,k;
ll a[505][505];
ll s[505][505]; //每一行的前缀和
ll ans=0;
int main()
{
cin>>n>>m>>k;
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=m;j++)
{
cin>>a[i][j];
s[i][j]+=s[i][j-1]+a[i][j]; //初始化每行前缀和
}
}
for(ll i=1;i<=m;i++) //固定一行中左起点
{
for(ll j=i;j<=m;j++) //固定一行中右终点
{
ll up=1;
ll sum=0;
for(ll down=1;down<=n;down++) //列举行数
{
sum+=s[down][j]-s[down][i-1];
while(sum>k)
{
sum-=s[up][j]-s[up][i-1];
up++;
}
ans+=down-up+1;
}
}
}
cout<<ans;
return 0;
}
七、积木画
题目



个人见解
线性dp
记得取模!!!好不容易写出来结果一组都过不掉,取个模就 ac 了。
AC代码(线性dp)
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll mod=1e9+7;
ll dp[10000005][3]; //0:只有第一行放了,1:只有第二行放了,2:两行全放了
int main()
{
ll n;
cin>>n;
dp[1][0]=0,dp[1][1]=0,dp[1][2]=1;
dp[2][0]=1,dp[2][1]=1,dp[2][2]=2;
for(ll i=3;i<=n;i++)
{
dp[i][0]=(dp[i-2][2]+dp[i-1][1])%mod;
dp[i][1]=(dp[i-2][2]+dp[i-1][0])%mod;
dp[i][2]=(dp[i-1][2]+dp[i-2][2]+dp[i-1][0]+dp[i-1][1])%mod;
}
cout<<dp[n][2]%mod;
return 0;
}
八、扫雷
题目


个人见解
无脑暴力即可。
AC代码
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m;
ll a[105][105];
ll ans[105][105];
ll dx[8]={0, 0,1,-1,1,-1,1,-1};
ll dy[8]={1,-1,0, 0,1,-1,-1,1};
int main()
{
cin>>n>>m;
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=m;j++)
{
cin>>a[i][j];
}
}
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=m;j++)
{
if(a[i][j]==1)
{
ans[i][j]=9;
continue;
}
else
{
for(ll k=0;k<8;k++)
{
ll x=i+dx[k],y=j+dy[k];
if(x<1||x>n||y<1||y>m)continue;
if(a[x][y]==1)ans[i][j]++;
}
}
}
}
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=m;j++)
{
cout<<ans[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
*九、李白打酒加强版
题目



个人见解
- 记忆化搜索+DFS代替dp的一天。
因为遇见花最多100次,我就把 cur 开到了 105 ,但是一定要记得判断 cur>m ,否则会数组越界!!! - 害,线性dp还是写下吧。
记得结果一定要输出 dp[n][m-1][1] (确保最后一次遇到的是花),如果输出 dp[n][m][0] 答案不对哦,会包含酒提前喝完,剩下遇见的全是店的情况。
AC代码(记忆化搜索+DFS)(线性dp)
- 记忆化搜索+DFS
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll mod=1e9+7;
ll st[105][105][105]; //cur,n,m
ll dfs(ll cur,ll n,ll m) //cur:酒余额,n:店次数,m:花次数
{
if(cur==1&&n==0&&m==1) //到达终点
{
return 1;
}
if(cur==0||cur>m) //无效结果
{
return 0;
}
if(st[cur][n][m]!=-1)return st[cur][n][m]; //该状态已经访问过
ll res=0;
if(n>0)res=(res+dfs(2*cur,n-1,m))%mod;
if(m>0)res=(res+dfs(cur-1,n,m-1))%mod;
st[cur][n][m]=res;
return res;
}
int main()
{
memset(st,-1,sizeof(st));
ll n,m;
cin>>n>>m;
cout<<dfs(2,n,m);
return 0;
}
- 线性dp
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll mod=1e9+7;
ll dp[105][105][105]; //遇见i次店次数,j次花,酒余额为k的方案次数
int main()
{
ll n,m;
cin>>n>>m;
dp[0][0][2]=1; //初始状态
for(ll i=0;i<=n;i++)
{
for(ll j=0;j<=m;j++)
{
for(ll k=0;k<=m;k++)
{
if(i>=1&&k%2==0)dp[i][j][k]=dp[i-1][j][k/2];
if(j>=1&&k>=1)dp[i][j][k]=(dp[i][j][k]+dp[i][j-1][k+1])%mod;
}
}
}
cout<<dp[n][m-1][1];
return 0;
}
*十、砍竹子
题目



个人见解
真的很搞,怎么都只能过65%的数据,结果只需要把 sqrt 改成 sqrtl 就 ac 了,见识短浅了,第一次听说这个函数,或者利用 long double 强转也可以。
double为双精度浮点数,意味着它能精确表示的十进制有效数字大约只有 15 - 17 位,而题目给的 h 可以达到 10 18 10^{18} 1018 ,也就是 19 位,低位的数字直接被舍掉了。
cpp
val=floor(sqrt((long double)(val/2+1)));
下面给大家提供了两种思路。
AC代码(链表+优先队列)(贡献法)
- 链表+优先队列
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=200005;
ll e[N],l[N],r[N],idx;
ll used[N];
//初始化
void init(){
l[1]=0;
r[0]=1;
idx=2;
}
//在节点a的右边插入一个数x
void insert(int a,int x){
e[idx]=x;
l[idx]=a,r[idx]=r[a];
l[r[a]]=idx,r[a]=idx++;
}
//删除节点a
void remove(int a){
l[r[a]]=l[a];
r[l[a]]=r[a];
}
int main()
{
ll n;
cin>>n;
init();
auto cmp=[](pair<ll,ll> a,pair<ll,ll> b){ //first:元素,second:元素下标
if(a.first==b.first)return a.second>b.second;
return a.first<b.first;
};
priority_queue<pair<ll,ll>,vector<pair<ll,ll>>,decltype(cmp)> q(cmp);
ll prev=0; //上一个不相等的下标
for(ll i=1;i<=n;i++)
{
ll h;
cin>>h;
if(h!=e[prev])
{
insert(prev,h);
prev=idx-1;
q.push({h,prev});
}
}
ll cnt=0;
while(!q.empty())
{
auto m=q.top();
q.pop();
ll val=m.first,pos=m.second;
if(val==1)break; //最大是1直接结束
if(used[pos])continue;
val=floor(sqrt((long double)(val/2+1)));
cnt++;
bool mleft=l[pos]!=0&&e[l[pos]]==val; //判断是否和左边相等
bool mright=r[pos]!=1&&e[r[pos]]==val; //判断是否和右边相等
if(mleft&&mright) //左右全相等,删自己和右边邻居
{
used[r[pos]]=1,remove(r[pos]);
used[pos]=1,remove(pos);
continue;
}
else if(mleft||mright) //左右只有一个和自己相等
{
remove(pos);
used[pos]=1;
continue;
}
e[pos]=val;
q.push({val,pos});
}
cout<<cnt;
return 0;
}
- 贡献法求解后删掉多余的次数即可,依然要用
long double!
cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=200005;
vector<ll> h[N];
int main()
{
ll n;
cin>>n;
ll cnt=0;
for(ll i=1;i<=n;i++)
{
ll hv;
cin>>hv;
while(hv!=1)
{
h[i].push_back(hv);
hv=floor(sqrt((long double)(hv/2+1)));
}
cnt+=h[i].size();
}
for(ll i=2;i<=n;i++)
{
if(h[i].empty()||h[i-1].empty())continue;
auto p1=h[i-1].begin(),p2=h[i].begin();
while(*p1!=*p2)
{
if(*p1>*p2)p1++;
else if(*p1<*p2)p2++;
if(p1==h[i-1].end()||p2==h[i].end())break;
}
if(p1!=h[i-1].end())cnt-=h[i].end()-p2;
}
cout<<cnt;
return 0;
}