A.2的N次方
题意:
给定正整数 x,输出 2 的 x 次方的个位。
题解:
找规律,容易发现从 2^1、2^2 直到 2^n,有 2、4、8、6 这样的规律循环呈现在每一个数的个位上。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
int x;
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
while(cin>>x)
{
int tp=x%4;
if(tp==1) cout<<2;
else if(tp==2) cout<<4;
else if(tp==3) cout<<8;
else cout<<6;
cout<<endl;
}
return 0;
}
B.连续自然数的和
题意:
给定 n 和 k 两个整数,然后输入 n 个数,组成一个序列,输出这个序列中共有多少组连续的数的和是 k。
题解:
计算前缀和数组 sum[],并统计每个 sum[i] 出现的次数。依题意可得 sum[R]-sum[L-1]=k,调换式子两边,有 sum[R]-k=sum[L-1],所以可以枚举每一个 R,然后累计对应的 sum[L-1] 出现的次数。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=1e5+10;
int n,k,a[N],sum[N];
map<int,int> mp;
signed main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
mp[0]=1;
for(int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+a[i];
mp[sum[i]]++;
}
int ans=0;
for(int i=n;i>=1;i--)
if(mp[sum[i]-k])
ans+=mp[sum[i]-k];
cout<<ans;
C.栈桥旅游
题意:
有 n 名游客要上船观光。游客编号 1 到 n。船的最大承重为 W。第 i 个人的重量为 x[i]。现在有多次游客上下船的操作,请统计一下整个过程中船所承受过的最大总重量是多少,如果该游客想要上船的时候发现船超重了,那么他就不能上船。
题解:
定义变量 res,表示某时刻船的承重。然后 O(N) 遍历,看当前游客能否上船,并同时更新船的最大承重 ans 即可。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=1e5+10;
int n,m,w,x[N],vis[N],ans;//vis[i]表示游客i是否在船上
signed main()
{
cin>>n>>m>>w;
for(int i=1;i<=n;i++) cin>>x[i];
int res=0;
for(int i=1,inx;i<=m;i++)
{
cin>>inx;
if(vis[inx])
{
vis[inx]=0;
res-=x[inx];
}
else{
if(res+x[inx]>w) continue;
vis[inx]=1;
res+=x[inx];
ans=max(ans,res);
}
}
cout<<ans;
return 0;
}
D.投喂修狗
题意:
有 n 只修狗,站成一排。每只修狗都有它的体积 v ,一般体积越大的修狗吃的粮食越多,同时修狗比较喜欢妒忌,它们会注意到自己左边第一只和右边第一只同伴的体积和被投喂的狗粮的数量,如果存在体积比自己瘦小还吃得不比自己少的它就会开始大吵大闹,小明不希望看到这样的情况发生,同时小明也想尽可能的节约一点狗粮,输出需要的最少狗粮数。
题解:
想象成一排各有高度的连绵的山群,我们先找到“谷底”,即体重的都比左右邻居小的狗,各以它们为起点向左向右遍历计算各只狗吃的狗粮数。第一次遍历完后仍有部分区间没有被计算,如以两山峰为左右端点且中间没有谷底的区间,记录这些区间,并进行第二次遍历,计算答案。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=5e4+10;
int n,a[N],ans,b[N];
vector<int> s;
struct node{
int l,r;
}m[N];
//数组m[]统计第一次遍历没有被遍历到的区间
//数组a[i]记录i修狗的体重,数组b[i]记录i修狗吃的狗粮数
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
//记录“谷底”
if(a[1]<a[2]) s.push_back(1);
for(int i=2;i<n;i++)
if(a[i]<a[i-1]&&a[i]<a[i+1])
s.push_back(i);
if(a[n]<a[n-1]) s.push_back(n);
//从每个“谷底”开始,向左向右遍历,计算答案
for(int i=0;i<s.size();i++)
{
int beg=s[i];
b[beg]=1;
//取max的原因是防止“山头”两侧深度不一样
for(int j=beg+1;a[j]>a[j-1]&&j<=n;j++) b[j]=max(b[j],b[j-1]+1);
for(int j=beg-1;a[j]>a[j+1]&&j>=1;j--) b[j]=max(b[j],b[j+1]+1);
}
//记录区间
int i,tot=0;
for(i=1;i<=n;i++)
{
if(!b[i])
{
tot++;
m[tot].l=i;
int j;
for(j=i+1;!b[j]&&j<=n;j++);
m[tot].r=j-1;
i=m[tot].r;
}
}
//第二次遍历
//Eg:1 2 3 3 3 4 5 4 3 3 3 2 1
for(int i=1;i<=tot;i++)
{
for(int j=m[i].l;j<=m[i].r;j++)
{
if(a[j]==a[j-1])
{
if(a[j]==a[j+1]||a[j]<a[j+1])
b[j]=1;
}
if(a[j]>a[j-1]) b[j]=b[j-1]+1;
}
for(int j=m[i].r;j>=m[i].l;j--)
{
if(b[j]) continue;
if(a[j]==a[j+1]) b[j]=1;
if(a[j]>a[j+1]) b[j]=max(b[j],b[j+1]+1);
}
}
for(int i=1;i<=n;i++) ans+=b[i];
cout<<ans;
return 0;
}
E.战前筹备
题意:
给定一张 n 个点, m 条边的简单图,每个点都有一个人,一个人可以机惨他身旁的所有人,若这一时刻一个人被机惨,那么下一时刻他会报复性机惨能机惨到的所有人(若一个时刻它被机惨多次只会报复一次),同时,如果编号为 i 的位置的人被机惨的次数大于等于这个位置的防御力 di, 他将因 AKIOI 而社死并退出战斗,即以后别人不会再机惨他,他也不会在下一轮报复机惨。若一开始由一号点开始机惨身边的所有人,输出每个位置的被机惨次数。
题解:
BFS。从节点 1 开始 BFS,计算每个节点被机惨的次数,细节在于需要记录每一个节点死亡的时间和报复的时间,由此来判断某节点是否能够对周围人发动机惨。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=105;
//数组ans[]记录各节点在遍历过程中受到的机惨次数
//数组die[]记录各节点死亡的时间
//数组st[]记录各节点开始机惨周围人的时间
int n,m,d[N],ans[N],die[N],tmp[N],st[N];
vector<int> V[N];
struct node{
int u,tim;//当前节点,当前时间
};
void bfs()
{
memset(die,0x3f,sizeof die );//初始化
queue<node> q;
q.push((node){1,1});
st[1]=1;
while(!q.empty())
{
int x=q.front().u;
int cur=q.front().tim;
q.pop();
//如果该节点在当前时间前就寄了,那么他也无法机惨周围人了
if(cur>=die[x]) continue;
for(int i=0;i<V[x].size();i++)
{
int y=V[x][i];
d[y]--;
//如果防御值为0,下一秒才是他的死亡时间
if(!d[y]) die[y]=cur+1;
//如果该节点还没死,那么他可以被机惨
if(cur<die[y]) ans[y]++;
//如果当前时间大于该节点上一次报复的时间,那么他可以开展下一次报复
if(cur>=st[y])
{
q.push((node){y,cur+1});
st[y]=cur+1;
}
}
}
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>d[i];
memcpy(tmp,d,sizeof d );
for(int i=1,x,y;i<=m;i++)
{
cin>>x>>y;
V[x].push_back(y);
V[y].push_back(x);
}
bfs();
//保证节点i最多被机惨d[i]次
for(int i=1;i<=n;i++) cout<<min(tmp[i],ans[i])<<" ";
return 0;
}
F.谍战情报
题意:
给定一个长度为 n 的序列,有 m 次询问,2 种操作。
1、把下标为 x 的数改成 y。
2、对于给定的下标区间 [L,R],输出其中小于 x 的数的个数。
题解:
分块。将序列分成 √n 块,用 vector 存储每一块里的元素并排序。对于操作 1,修改元素后得对对应的块重新排序。对于操作 2,有被给定区间完全覆盖的块,直接二分查询,没有被完全覆盖的区间则暴力查询。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N=1e5+10;
int n,m,len,a[N],inx[N];//数组inx[i]记录a[i]所在的块的编号
vector<int> S[N];//记录每个块里的元素
void build(int n)
{//创建块
len=n/sqrt(n);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
inx[i]=(i-1)/len+1;
S[inx[i]].push_back(a[i]);
}
for(int i=1;i<=inx[n];i++) sort(S[i].begin(),S[i].end());
}
int cal(int k) { return (k-1)*len+1; }//返回每一个块的起始点
void modify(int k)
{//给块重新排序
S[k].clear();
for(int i=cal(k);i<=min(cal(k+1)-1,n);i++) S[k].push_back(a[i]);
sort(S[k].begin(),S[k].end());
}
int query(int l,int r,int x)
{
int cnt=0;
int posl=inx[l],posr=inx[r];
if(posl==posr)
{//如果查询区间都在同一个块里,直接暴力统计
for(int i=l;i<=r;i++)
if(a[i]<x)
cnt++;
}
else{
//左边没有被完全覆盖的块
for(int i=l;i<cal(posl+1);i++)
if(a[i]<x)
cnt++;
//被询问区间完全覆盖的块
for(int i=posl+1;i<posr;i++)
cnt+=lower_bound(S[i].begin(),S[i].end(),x)-S[i].begin();
//右边没有被完全覆盖的块
for(int i=cal(posr);i<=r;i++)
if(a[i]<x)
cnt++;
}
return cnt;
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
cin>>n>>m;
build(n);
for(int i=1,op,x,y,l,r;i<=m;i++)
{
cin>>op;
if(op==1)
{
cin>>x>>y;
a[x]=y;
modify(inx[x]);
}
else{
cin>>l>>r>>x;
cout<<query(l,r,x)<<endl;
}
}
return 0;
}
G.绝地反击
题意:
给定一个圆,圆上有 n 个点,输出由这 n 个点构成的 n 边形完全图中的三角形个数。
题解:
考虑三角形的构成:1、三个顶点都在圆上;2、只有两个顶点在圆上(相当于一个内接四边形中的 4 个三角形);3、只有一个顶点在圆上(相当于一个内接五角星形里的 5 个三角形);4、没有顶点在圆上(相当于一个内接六边形中由三条对角线构成的 1 个三角形)。所以有式如下:
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int mod=1e8+7;
int n;
//30138891 720模1e8+7下的逆元
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
cin>>n;
if(n==0||n==1||n==2) cout<<0;
else if(n==3) cout<<1;
else if(n==4) cout<<8;
else if(n==5) cout<<35;
else cout<<(n*(n-1)%mod*(n-2)%mod)*(120*(n-2)%mod+(n-3)*(n-4)%mod*(n+25)%mod)%mod*30138891%mod;
return 0;
}
H.那一天我们许下约定
题意:
我拿走她所有的饼干共 N 块,在从今天起不超过 D 天的时间里把所有的饼干分次给她,每天给她的饼干数要少于M 以防止她吃太多。输出有多少种方案来把饼干分给我的她。
题解:
DP。dp[i][j]:将 j 块饼干分到 i 天里的方案数,有如下状态转移方程:
时间复杂度 O(N^3),考虑优化。观察发现,dp[i][j] 都是由上一层一段连续的数之和转移来的,我们可以用前缀和将等式右边枚举优化掉,最终有如下状态转移方程:
最后,在计算答案时,需要注意分到饼干的这 i 天在 D 天里的组合对答案的贡献。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int mod=998244353;
//dp[i][j]:将j块饼干分到i天里的方案数
int n,D,m,dp[2005][2005],sum[2005];
int qpow(int x,int k)
{//求逆元
int res=1;
while(k)
{
if(k&1) res=x*res%mod;
x=x*x%mod;
k>>=1;
}
return res%mod;
}
void solve()
{
if(n==0&&D==0&&m==0) return ;
int ans=0;
memset(dp,0,sizeof dp );
dp[0][0]=1;//边界
for(int i=1;i<=min(n,m-1);i++) dp[1][i]=1;
for(int i=2;i<=min(n,D);i++)
{
for(int j=0;j<=n;j++)//计算前缀和
sum[j]=(sum[j-1]%mod+dp[i-1][j]%mod)%mod;
for(int j=1;j<=n;j++)
dp[i][j]=(sum[j-1]%mod-sum[max(j-m,0ll)]%mod+mod)%mod;
}
int C=D%mod;
for(int i=1;i<=min(n,D);i++)
{//考虑分到饼干的这i天在D天中的组合
if(i==1) (ans+=dp[i][n]*C%mod)%mod;
else{
C=((C*qpow(i,mod-2)%mod)*((D-i+1)%mod))%mod;
(ans+=dp[i][n]*C%mod)%mod;
}
}
cout<<(ans+mod)%mod<<endl;
}
signed main()
{
while(cin>>n>>D>>m) solve();
return 0;
}
I.那一天她离我而去
题意:
给定一张由 n 个节点 m 条边组成的无向图。从 1 号节点出发,找出从出发点到出发点的最小环。
题解:
最短路。将与 1 号节点相连的边删去,以某个和 1 号节点相连的点 u 为起点,跑 Dijkstra,去枚举其他和 1 号节点相连的点 v,计算答案。
计算答案时的复杂度为 O(N*(MlogM+N)),如果碰上菊花图则会超时,考虑优化。我们从节点编号的二进制表示上入手,将那些和 1 号节点相连的节点重新编号,然后考虑 1(1)、2(10)、4(100)、8(1000)……编号为这样的节点各自和 1 号节点相连,而其他剩余节点和一个新节点(n+1号节点)相连时对答案的贡献,如下图所示,其中红色边去原边(虚线)等值。时间复杂度化为 O(logN*(MlogM+N))。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define INF 0x3f3f3f3f
const int N=1e4+10;
int T,n,m,ans,vis[N],dis[N];
struct edge{
int v,w,next;
}e[8*N];
int tot,cnt,head[N],tmp[N];
void add(int from,int to,int w)
{
e[++cnt].v=to;
e[cnt].w=w;
e[cnt].next=head[from];
head[from]=cnt;
}
struct element{
int u,w;
}S[N];
struct node{
int u,w;
bool operator < (node a) const { return w>a.w; }
};
void dijkstra(int s)
{
memset(vis,0,sizeof vis );
memset(dis,INF,sizeof dis );
dis[s]=0;
priority_queue<node> q;
q.push((node){s,dis[s]});
while(!q.empty())
{
node x=q.top();
q.pop();
if(vis[x.u]) continue;
vis[x.u]=1;
for(int i=head[x.u];i;i=e[i].next)
{
int y=e[i].v;
if(dis[y]>x.w+e[i].w)
{
dis[y]=x.w+e[i].w;
q.push((node){y,dis[y]});
}
}
}
}
void solve()
{
tot=0,cnt=0,ans=INF;
memset(head,0,sizeof head );
cin>>n>>m;
for(int i=1,x,y,z;i<=m;i++)
{
cin>>x>>y>>z;
if(x>y) swap(x,y);
if(x==1)
{//记录所有和1号节点相连的节点信息
S[++tot]={y,z};
continue;
}
add(x,y,z),add(y,x,z);
}
memcpy(tmp,head,sizeof head );
for(int i=0;(1<<i)<=tot;i++)//按位枚举
{
memcpy(head,tmp,sizeof tmp );
for(int j=1;j<=tot;j++)
{
if(j&(1<<i)) add(1,S[j].u,S[j].w);
else add(S[j].u,n+1,S[j].w);//其他点和n+1号节点相连
}
dijkstra(1);
ans=min(ans,dis[n+1]);
}
if(ans==INF) ans=-1;
cout<<ans<<endl;
}
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
cin>>T;
while(T--) solve();
return 0;
}