自学内容网 自学内容网

SICNU五一赛题解

题目名称知识点
Aa+b思维
B倒转乾坤签到
C不水杯dfs
D简单硬币思维题,进制
E子矩阵双指针,前缀和
F围墙bfs
GMH学长的旅行最短路
HWYR学长理财二分答案
I传纸条线性dp

难度预估:B、A < D、E、H 、C < I、G、F

A、a+b

#include<bits/stdc++.h>
#define ll long long
using namespace std;
map<ll,ll> cnt;
int main(){
    int n,c;
    cin >> n >> c;
    for(int i = 1 ; i <= n ; i ++){
        int x; cin >> x;
        cnt[x]++;
    }
    ll ans = 0;
    for(auto [x,y] : cnt)
        if(cnt.count(c-x)){
            if(x!=c-x)ans += y*cnt[c-x];
            else ans += (y-1)*cnt[c-x]; 
        }
    cout << ans;
    return 0;
}

B、倒转乾坤

可以观察到,运动轨迹始终在直径上,所以答案是 4 n d 4nd 4nd。此题是去年四川省赛的原签到题。

D、 简单硬币

贪心思维题,当1到x-1的硬币都能够凑齐,但x的值凑不齐时,我们可以用一枚x面值的硬币,使得前面所有硬币和他组合,能够凑齐x+1到 2x-1面值的硬币。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e4+10;
int coin[N];

int main()
{
int n, target;
cin >> n >> target;

for(int i = 0; i < n; i ++ )
cin >> coin[i];

sort(coin, coin+n);

int num = 0, s = 0;
for(int i = 0; i < n && s < target; i ++ )
{
while(coin[i] > s+1)
{
s += s+1;
num ++;
if(s >= target) break;
}
s += coin[i];
}
while(s < target)
{
s += s+1;
num ++;
}
cout << num;
}

E、子矩阵

#include <bits/stdc++.h>
#define N 10001
using namespace std;
long long sum=0;
int a[N][N];
 
int main()
{
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
a[i][j]+=a[i-1][j];
}
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
int s=0;
for(int l=1,r=1;r<=m;r++){
s+=a[j][r]-a[i-1][r];
while(s>k){
s-=a[j][l]-a[i-1][l];
l++;
}
sum+=r-l+1;
}
}
}
cout<<sum<<endl;
return 0;
}

H、 WYR学长理财

标准的二分答案

#include <bits/stdc++.h>
#define ll long long
using namespace std; 
const int N=1e5+5;
ll ans=0;
ll len[N];
int n,k;
//check函数,用来判断mid是否满足条件,是二分答案的核心和难点所在 
int check(int mid)
{
int sum=0;//计算能切成多少长为mid的小木条 
for(int i=1;i<=n;i++){
sum+=(len[i]/mid);
}
if(sum<k) return 0;//小于k则不满足 
if(sum>=k) return 1;//大于k则满足 
}
int main()
{
std::ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>len[i];
//答案区间[1,1e8] 
ll l=1,r=1e8;
//ans初始化以便于判断是否有答案 
ans=-1;
while(l<=r){
ll mid=(l+r)/2;
if(check(mid)){
//mid满足切成k段,则ans先记为mid 
ans=mid;
//去更大的区间找是否有满足的,以保证最后时最大值 
l=mid+1;
}
//密度不满足能切成k段,则去更小的区间找是否有满足的 
else r=mid-1; 
}
if(ans==-1) cout<<0;
else cout<<ans;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define inf 0x3f3f3f3f
const int mod = 998244353;
const int N = 1e5 + 10;
int n, k;
int a[N];
bool check(int mid){
    int res = 0;
    for(int i = 1 ; i <= n ; i ++) res += a[i]/mid;
    return res >= k;
}
void solve(){
    cin >> n >> k;
    int mx = 0;
    for(int i = 1 ; i <= n ; i ++) {
        cin >> a[i];
        mx = max(a[i],mx);
    }
    int l = 0 , r = mx;
    while(l < r){
        int mid = l + r + 1 >> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    cout << l;
}   
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int _ = 1;
    //cin >> _;
    while(_--) solve();
    return 0;
}

C、不水杯

本题题目大家可能会想到写贪心,其实贪心是错误的,考虑以下样例:

选手A目前3分、选手B目前2分、选手C目前2分,B和C还要进行两场比赛,发现这种情况A一定不会是第一名;

事实上本题正确做法是dfs,因为每组用例的比赛局数 m ( ≤ 10 ) m(\leq10) m(10) 很小,所以直接用dfs枚举

每一局的结果就可以做到 𝑂(3^{𝑚}𝑇) 的复杂度,可以通过本题

所以大家要学会看数据范围做题,当看到数据范围是dfs可过、这个dfs又不难写的时候,就别想别的了,直接dfs启动

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
const int MAX=100+10;
struct node{
    int idx,score;
} a[MAX];
int ans,cnt;
int n, m;
vector<pair<int,int> >  res;
void dfs(int step){
    if(step == cnt ){
        int rank = 1;
        for(int i = 2 ; i <= n ; i ++)
        {
            if(a[i].score > a[1].score) rank ++;
        }
        ans = min(ans,rank);
        return;
    }
    //对于每场没有1,x和y的比赛,有三种情况x胜利,y胜利,打平
    for(int i = 1 ; i <= 3; i ++){
        if(i == 1){
            a[res[step].fi].score += 3;
            dfs(step + 1);
            a[res[step].fi].score -= 3;
        }else if(i == 2){
            a[res[step].se].score += 3;
            dfs(step + 1);
            a[res[step].se].score -= 3;
        }else{
            a[res[step].fi].score += 1;
            a[res[step].se].score += 1;
            dfs(step + 1);
            a[res[step].fi].score -= 1;
            a[res[step].se].score -= 1;
        }
    }
}
void solve()
{
    res.clear();
    cnt = 0;
    ans = INT_MAX;
    cin >> n >> m;
    for(int i = 1 ; i <= n ; i ++) {
        cin >> a[i].score;
        a[i].idx = i;
    }
    int mm = m;
    while(mm--)
    {
        int x ,y;
        cin >> x >> y;
        if(x == 1 || y == 1) a[1].score += 3;//如果有和1比的就让1胜
        else {//否则dfs剩下的
            res.push_back({x,y}); 
            cnt ++;
        }
    }
    dfs(0);
    cout << ans << "\n";
}
int main()
{
    int _;
    cin >> _;
    while(_--) solve();
    return 0;
}

I、传纸条

d p i , j dp_{i,j} dpi,j表示传到点 i , j i,j i,j的最大路径条数,根据题目可以标记不能到达的点为vis=1, d p i , j = 0 dp_{i,j}=0 dpi,j=0,否则 d p i , j = d p i − 1 , j + d p i , j − 1 dp_{i,j}=dp_{i-1,j}+dp_{i,j-1} dpi,j=dpi1,j+dpi,j1, d p x , y dp_{x,y} dpx,y即为答案

#include<bits/stdc++.h>
#define ll long long
using namespace std;
// 马能够控制的位置 
const int fx[] = {0, -2, -1, 1, 2, 2, 1, -1, -2};
const int fy[] = {0, 1, 2, 2, 1, -1, -2, -2, -1};
int bx, by, mx, my;
ll f[40][40];
bool s[40][40];
int main(){
    scanf("%d%d%d%d", &bx, &by, &mx, &my);
    bx += 2; by += 2; mx += 2; my += 2;
    f[2][1] = 1;
    s[mx][my] = 1;
    for(int i = 1; i <= 8; i++) s[mx + fx[i]][my + fy[i]] = 1;
    for(int i = 2; i <= bx; i++)
{
        for(int j = 2; j <= by; j++)
{
            if(s[i][j]) continue;
            f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
    }
    printf("%lld\n", f[bx][by]);
    return 0;
}

G、 MH学长的旅行

#include<cstdio>
#include<cctype>
#include<cstring>
#include<queue>
#include<algorithm>
#include<vector>
#include<utility> 
#include<functional>

int Read()
{
    int x=0;char c=getchar();
    while(!isdigit(c))
    {
        c=getchar();
    }
    while(isdigit(c))
    {
        x=x*10+(c^48);
        c=getchar();
    }
    return x;
}

using std::priority_queue;
using std::pair;
using std::vector;
using std::make_pair;
using std::greater;

struct Edge
{
    int to,next,cost;
}edge[4500001];
int cnt,head[210005];

void add_edge(int u,int v,int c=0)
{
    edge[++cnt]=(Edge){v,head[u],c};
    head[u]=cnt;
}

int dis[210005];
bool vis[210005];
void Dijkstra(int s)
{
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > points;
    points.push(make_pair(0,s));
    while(!points.empty())
    {
        int u=points.top().second;
        points.pop();
        if(!vis[u])
        {
            vis[u]=1;
            for(int i=head[u];i;i=edge[i].next)
            {
                int to=edge[i].to;
                if(dis[to]>dis[u]+edge[i].cost) 
                {
                    dis[to]=dis[u]+edge[i].cost;
                    points.push(make_pair(dis[to],to));
                }
            }
        }
    }
}

int main()
{
    int n=Read(),m=Read(),k=Read(),s=Read(),t=Read();
    int u,v,c;
    for(int i=0;i<m;++i)
    {
        u=Read(),v=Read(),c=Read();
        add_edge(u,v,c);
        add_edge(v,u,c);
        for(int j=1;j<=k;++j)
        {
            add_edge(u+(j-1)*n,v+j*n);
            add_edge(v+(j-1)*n,u+j*n);
            add_edge(u+j*n,v+j*n,c);
            add_edge(v+j*n,u+j*n,c);
        }
    }
    for(int i=1;i<=k;++i)
{
add_edge(t+(i-1)*n,t+i*n);
}//预防奇葩数据
    Dijkstra(s);
    printf("%d",dis[t+k*n]);
    return 0;
}

F、围墙

这个题主要思路就是两部分,第一要判断能不能走到围墙下面,第二就是要记录围墙代表的#的个数和岗哨代表的#的个数(这个还是很好区分,因为题目保证了一个岗哨四周不会出现别的岗哨也就是一个#的四个方向上不会有#,如果有就代表它一定是围墙)。

第一部分判断可以用 b f s bfs bfs去判断能不能走到,也可以用 d f s dfs dfs

第二部分计算可以用 b f s bfs bfs或者 d f s dfs dfs去搜围墙代表的#的个数,也可以直接根据围墙和岗哨的区别去遍历地图上每一个#来判断这个#具体代表的是围墙还是岗哨。(后者要简单一点,如果比赛的时候用后者写的可以再用前面的 b f s bfs bfs或者 d f s dfs dfs思路写一下,加深理解。)

#include <bits/stdc++.h>
using namespace std;

const int N=110;
int n,m;
int stx,sty,xx,yy;//起始点坐标、bfs走到的第一个围墙#坐标 
char mp[N][N];//记录地图 
bool pd[N][N];//标记点走没走过 
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0}; 
int cnt=0;//记录#的个数 
int res_cnt=1;//记录围墙面积(围墙#个数) 

//用来判断这个点是不是围墙 
bool check(int x,int y){ 
for(int i=0;i<4;i++){ //遍历这个点的周围四个方向 
int nex=x+dx[i],ney=y+dy[i];
if(nex<1||ney<1||nex>n||ney>m)return false; //越界了 
if(mp[nex][ney]=='#')//说明x,y这个点四周有#存在 说明这个点必是围墙 
return true;
}
return false;
}

//正常bfs的check逻辑,来判断有没有越界或者走没走过 
bool check_1(int x,int y){
  if(x<1||y<1||x>n||y>m)return false;
if(pd[x][y])return false;
return true;
}

//先判断能不能走到围墙周围,也可以用dfs 
bool bfs_1(){   
queue<pair<int,int>>q;
q.push({stx,sty});
pd[stx][sty]=1;

//正常的bfs过程 
while(!q.empty()){
auto t=q.front();
q.pop();
int nowx=t.first,nowy=t.second;

for(int i=0;i<4;i++){ //遍历四个方向 
int nex=nowx+dx[i];
int ney=nowy+dy[i];

if(check_1(nex,ney)){ //判断走没走过这个点或者这个点有没有越界 
if(mp[nex][ney]=='#'){//如果遇到了 # 去判断是不是岗哨 
if(check(nex,ney)){ //如果是围墙 
xx=nex;//记录下来这个围墙的坐标,下一步记录围墙面积可以从这个点开始 
yy=ney;
return true;//找到了,直接返回 
}else
continue;
}

pd[nex][ney]=1;
q.push({nex,ney});
} 
}
}
return false;//走不到围墙 
}

//第二次bfs计算围墙面积的check,保证只能走有#的格子 
bool check_2(int x,int y){
  if(x<1||y<1||x>n||y>m)return false;//越界 
if(pd[x][y])return false;//走过了 
if(mp[x][y]!='#')return false;//不是# 

return true;
}

//用来计算围墙#的个数 
void bfs_2(int xx,int yy){
queue<pair<int,int>>q;
q.push({xx,yy});
pd[xx][yy]=1;

//正常bfs过程 
while(!q.empty()){
auto t=q.front();
q.pop();
int nowx=t.first,nowy=t.second;

for(int i=0;i<4;i++){
int nex=nowx+dx[i];
int ney=nowy+dy[i];

if(check_2(nex,ney)){//只能走有#的格子 
res_cnt++;//围墙#的个数加一 
pd[nex][ney]=1;
q.push({nex,ney});
} 
}
}
}

void solve()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
cin>>mp[i][j];
if(mp[i][j]=='M'){
stx=i;//找起始点位置 
sty=j;
}
if(mp[i][j]=='#')cnt++;//记录#的总个数
}

if(!bfs_1()){//走不到围墙 
cout<<"Game over!"<<endl;
}else{
memset(pd,0,sizeof pd);//记得初始化标记数组 
bfs_2(xx,yy);//这一次的bfs_2是用来记录围墙的面积就是res_cnt
cout<<"yeah!"<<endl;
cout<<cnt*5-4*res_cnt<<endl;//#的总个数乘5减去围墙个数乘4就是最终答案 
} 
}

void init(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}

int main()
{
init(); 
    solve();
    
    return 0;
}


原文地址:https://blog.csdn.net/XUE_DING_E/article/details/142629337

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!