Acwing. 秋季每日一题

Acwing. 秋季每日一题

活动链接

A 重复局面.

国际象棋在对局时,同一局面连续或间断出现 3次或 3次以上,可由任意一方提出和棋。
国际象棋每一个局面可以用大小为 8×8的字符数组来表示,其中每一位对应棋盘上的一个格子。
六种棋子王、后、车、象、马、兵分别用字母 k、q、r、b、n、p表示,其中大写字母对应白方、小写字母对应黑方。
棋盘上无棋子处用字符 * 表示。
两个字符数组的每一位均相同则说明对应同一局面。
现已按上述方式整理好了每步棋后的局面,试统计每个局面分别是第几次出现。

A思路:

这是一个哈希表简单的应用题,我们可以将局面整个按照第一行第二行存储下来,记录每个局面出现的次数.

A代码:

#include<bits/stdc++.h>
using namespace std;
void solve(){
    unordered_map<string, int> map;
    int n;
    cin>>n;
    while(n--){
        string x="";
        string a;
        for(int i=1;i<=8;i++){
            cin>>a;
            x+=a;
        }
        map[x]++;
        cout<<map[x]<<endl;
        
    }
    
}
int main(){
    int t=1;
    while(t--){
        solve();
    }
    return 0;
    
}

B垦田计划

顿顿总共选中了 n块区域准备开垦田地,由于各块区域大小不一,开垦所需时间也不尽相同。据估算,其中第 i块(1≤i≤n)区域的开垦耗时为 ti天。
这 n块区域可以同时开垦,所以总耗时 tTotal取决于耗时最长的区域,即:

\[Ttoal=max(t1,t2,t3.....) \]

为了加快开垦进度,顿顿准备在部分区域投入额外资源来缩短开垦时间。
具体来说:
在第 i块区域每投入 ci单位资源,便可将其开垦耗时缩短 1天;
耗时缩短天数以整数记,即第 i块区域投入资源数量必须是 ci的整数倍;
在第 i块区域最多可投入 ci×(ti−k)单位资源,将其开垦耗时缩短为 k天;
这里的 k表示开垦一块区域的最少天数,满足 0<k≤min{t1,t2,…,tn};换言之,如果无限制地投入资源,所有区域都可以用 k天完成开垦。
现在顿顿手中共有 m单位资源可供使用,试计算开垦 n块区域最少需要多少天?

B思路+代码:

非二分做法:
如果想要将耗时时间降低一天,我们就需要将所有最大天数降低一天,如果我们手里的资源不够的话,那就无法降低一天。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
// struct node
// {
// 	int t,c;

// }a[N];
// bool cmp(node a,node b){
// 	if()
// }
int m1[N];

void solve(){
	int n,m,k;
	cin>>n>>m>>k;
	int maxn=0;
	for(int i=1;i<=n;i++){
		int t,c;
		cin>>t>>c;
		maxn=max(maxn,t);
		m1[t]+=c;
	}
	for(int i=maxn;i>=k;i--){
		if(m<m1[i]){
			cout<<i<<endl;
			return ;

		}
		else{
			m-=m1[i];
			m1[i-1]+=m1[i];//所有最大天数减一,则减一的天数要加上之前最大天数的数量
		}
	}
	cout<<k<<endl;
	return ;

}
int main(){
	int t;
	// cin>>t;
	t=1;

	while(t--){
		solve();
	}
	return 0;

}

二分做法:
因为最小天数必须是k,则我们二分的时候就可以将k作为l的值,r的值即为所有垦田当中最大的天数。之后进行二分即可。这里可以用排序进行一个简单的优化,就是说我们在二分到mid天时候,如果后面垦田的天数本身比mid小,就不需要再判断了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,m,k;

struct node
{
	int t,c;

}a[N];
bool cmp(node a,node b){
	if(a.t==b.t){
		return a.c>b.c;

	}
	else{
		return a.t>b.t;
	}

}
bool check(int x){
	ll ans=0;
	for(int i=1;i<=n;i++){
		if(x>=a[i].t){
			break;
		}
		else{
			ans+=a[i].c*(a[i].t-x);
		}
	}
	if(ans<=m){
		return true;
	}
	else{
		return false;
	}
}

void solve(){
	// int n,m,k;
	cin>>n>>m>>k;
	int l=k;//最少得是k天
	int r=0;
	for(int i=1;i<=n;i++){
		int t,c;
		cin>>t>>c;
		r=max(t,r);
		a[i]={t,c};
	}
	sort(a+1,a+n+1,cmp);
	while(l<r){
		int mid=(l+r)/2;
		if(check(mid)){
			r=mid;
		}
		else{
			l=mid+1;

		}
	}
	cout<<l<<endl;
	return ;

}
int main(){
	int t1;
	// cin>>t;
	t1=1;

	while(t1--){
		solve();
	}
	return 0;

}

C题CCC单词搜索

具体问题
给定一个 R×C的大写字母矩阵。
请你在其中寻找目标单词 W。
已知,目标单词 W由若干个不同的大写字母构成。
目标单词可以遵循以下两种规则,出现在矩阵的水平、垂直或斜 45度线段中:
单词出现在一条线段上。
单词出现在两条相互垂直且存在公共端点的线段上。也就是说,单词首先出现在某线段上,直到某个字母后,转向 90度,其余部分出现在另一条线段上。
具体可以参照图例。
请你计算,目标单词在给定矩阵中一共出现了多少次。

C思路:

可以看出这是一个dfs的搜索问题,我们可以枚举八个方向,因为他说斜着也是可以的,然而这个题也是一个比较特殊的题,就是我们可以90度变换一次方向(仅限一次),也就是一个单词在两条直线上,所以我们也可以为这个开一个标记,表示一条方向上搜索一次,转弯搜索一次。

C代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 110;
char s[N][N];
string w;
int n, m;
int res;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int ix[4] = {-1, -1, 1, 1}, iy[4] = {-1, 1, 1, -1};
void dfs(int x, int y, int t, int f, int d, int k){
    if(t == w.size() - 1 && k < 2){  //题目中要求两个线段或一个线段,即k<2
        res ++;
        return;
    }
    if(f == -1){ //竖直水平方向搜索
        for(int i = 0; i < 4; i ++){
            int a = x + dx[i], b = y + dy[i];
            if(a < 0 || a >= n || b < 0 || b >= m) continue;
            if(s[a][b] == w[t + 1]){
                if(t != 0 && i != d)dfs(a, b, t + 1, -1, i, k + 1); //需转弯的情况
                else dfs(a, b, t + 1, -1, i, k); //搜索第一次或者没转弯的情况
            }
        }
    }
    if(f == 1){ //斜着方向搜索 原理同上
        for(int i = 0; i < 4; i ++){
            int a = x + ix[i], b = y + iy[i];
            if(a < 0 || a >= n || b < 0 || b >= m) continue;
            if(s[a][b] == w[t + 1]){
                if(t != 0 && i != d)dfs(a, b, t + 1, 1, i, k + 1);
                else dfs(a, b, t + 1, 1, i, k);
            }
        }
    }
}
int main(){
    cin >> w;
    cin >> n >> m;

    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j ++)
            cin >> s[i][j];

    for(int i = 0; i < n; i ++){
        for(int j = 0; j < m; j ++)
            if(s[i][j] == w[0]){
                //进行两种搜索方式
                dfs(i, j, 0, -1, 0, 0);
                dfs(i, j, 0, 1, 0, 0);
            }
    }
    cout << res << endl;
    return 0;
}

D对称山脉


有 N座山排成一排,从左到右依次编号为 1∼N。
其中,第 i座山的高度为 hi。对于一段连续的山脉,我们使用如下方法定义该段山脉的不对称值。如果一段连续的山脉由第 l∼r(1≤l≤r≤N)座山组成,那么该段山脉的不对称值为

\[∑|hl+i−hr−i|(0≤i≤(r−l)/2) \]

现在,你需要回答 N个问题,问题编号 1∼N。
其中,第 i个问题的内容是:请计算,一段恰好包含 i座山的连续山脉(即长度为 i的连续区间)的不对称值的最小可能值。

D思路:

这是一个比较明显的dp问题,我们要找的是连续i座山脉的不对称值的最小可能值。因此我们就可以做一个二维背包的转换,因此我们可以得到一个状态转移方程式(端点对称之差加上除去端点的dp背包)

\[dp[i][j]=abs(a[i+j-1]-a[i])+dp[i+1][j-1]; \]

这是我们简单的状态转移方程式,但是实际上我们需要在程序中稍微变一下型。

\[dp[i][i+j-1]=abs(a[i]-a[i+j-1])+dp[i+1][i+j-2]; \]

D代码

#include<bits/stdc++.h>
using namespace std;
const int N=5010;
int a[N];
int dp[N][N];
int ans[N];
void solve(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];

    }
    memset(ans,0x3f,sizeof ans);
    
    for(int j=2;j<=n;j++){
        for(int i=1;i<=n-j+1;i++){
            dp[i][i+j-1]=abs(a[i]-a[i+j-1])+dp[i+1][i+j-2];
            ans[j]=min(ans[j],dp[i][j+i-1]);
        }
    }
    ans[1]=0;
    for(int i=1;i<=n;i++){
        cout<<ans[i]<<" ";
    }
    cout<<endl;
    return ;

}
int main(){
    int t;
    t=1;
    while(t--){
        solve();
    }
    return 0;
}

E所有三角形

题目链接具体信息请结合原出题地址查看。
建筑商波奇刚刚完成了她的最新作品:一条精美的巷道。
此巷道由两排瓷砖组成,每排都恰好包含 C个边长为 1的白色等边三角形瓷砖。
其中,上排左起第一个三角形瓷砖指向上方,每对相邻三角形瓷砖(即包含公共边的三角形瓷砖)的指向都相反(可参照图例)。
不幸的是,她不小心打翻了一桶黑色油漆,使得其中一些三角形瓷砖被染黑了。
由于被染黑的瓷砖油漆未干,她计划使用胶带将所有染黑区域的边缘围住,以防别人误踩。
请你计算,她需要使用多少米的胶带。
输入格式
第一行包含整数 C。
第二行包含 C个整数 0或 1,表示第一排每个瓷砖的颜色。如果第 i个整数为 1,则表示第 i个瓷砖(左起)为黑色,如果第 i个整数为 0,则表示第 i个瓷砖(左起)为白色。
第三行包含 C个整数 0或 1,表示第二排每个瓷砖的颜色。如果第 i个整数为 1,则表示第 i个瓷砖(左起)为黑色,如果第 i个整数为 0,则表示第 i个瓷砖(左起)为白色。

E思路:

image

这个就是铺瓷砖的方式,因为我们需要把变黑的瓷砖圈起来,因此我们只需要看一个黑色区域中每个三角形贡献出几条边就可以。只需要简单的模拟一下就可以了。

E代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int a[3][N];

void solve(){
    int c;
    cin>>c;
    for(int i=1;i<=2;i++){
        for(int j=1;j<=c;j++){
            cin>>a[i][j];            
        }
    }
    int ans=0;
    for(int i=1;i<=c;i++){
        if(!a[1][i]){
            continue;
        }
        int cnt=3;
        if(i%2){
            if(a[2][i]){
                cnt--;
            }
        }
        if(a[1][i-1]){
            cnt--;
        }
        if(a[1][i+1]){
            cnt--;
        }
        ans+=cnt;

    }
    for(int i=1;i<=c;i++){
        if(!a[2][i]){
            continue;
        }
        int cnt=3;
        if(i%2){
            if(a[1][i]){
                cnt--;
            }
        }
        if(a[2][i-1]){
            cnt--;
        }
        if(a[2][i+1]){
            cnt--;
        }
        ans+=cnt;
        
    }
    cout<<ans<<endl;
    
}
int main(){
    int t;
    t=1;
    while(t--){
        solve();

    }
    return 0;

}

F题 分组

题目传送门
某班一共有 3G个学生,按照三人一组的规则,被分成了 G个小组。
同学之间关系有好有坏,分组结果可能会违背一些同学的意愿。
已知,同学两两之间一共有 X个同组意愿和 Y个不同组意愿。
每个同组意愿涉及两个同学,表示这两个同学强烈要求必须分在同一组。
每个不同组意愿涉及两个同学,表示这两个同学强烈要求必须分在不同组。
请你计算,所有 X+Y个意愿当中,一共有多少个意愿没有得到满足。
输入格式
第一行包含整数 X,表示一共有 X个同组意愿。
接下来 X行,每行包含两个同学的姓名,表示这两个同学强烈要求必须分在同一组。
再一行包含整数 Y,表示一共有 Y个不同组意愿。
接下来 Y行,每行包含两个同学的姓名,表示这两个同学强烈要求必须分在不同组。
输入保证,不含重复信息或矛盾信息,即同一对同学不会出现两次。
再一行包含一个整数 G,表示一共分成了 G组。
最后 G行,每行包含三个同学的姓名,表示这三个同学被分在了一组。
此班级的所有同学的名字都在最后 G行给出了,输入中不会出现不在班级内的名字。
输入保证,每个同学只被分在一组。
每个名字由 1∼10个大写字母组成,不同学生的名字不同。

F思路:

初看题意,认为这就是一个比较简单的模拟,事实上也确实是如此,只是我们需要用哈希表来给名字定义一些序号。

F代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
typedef pair<string,string > PSS;
PSS s1[N],s2[N];

unordered_map<string,int> mp;


void solve(){
    int x;
    cin>>x;
    for(int i=1;i<=x;i++){
        string a,b;
        cin>>a>>b;
        s1[i]={a,b};
    }
    int y;
    cin>>y;
    for(int i=1;i<=y;i++){
        string a,b;
        cin>>a>>b;
        s2[i]={a,b};

    }
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        string a,b,c;
        cin>>a>>b>>c;
        mp[a]=i;
        mp[b]=i;
        mp[c]=i;
        //表示这三个人是一个组的
    }
    //现在已经将数据存储好了
    //接下来就是处理数据
    int ans=x;
    for(int i=1;i<=x;i++){
        string a=s1[i].first;
        string b=s1[i].second;
        if(mp[a]==mp[b]){
            ans--;

        }
    }
    for(int i=1;i<=y;i++){
        string a=s2[i].first;
        string b=s2[i].second;
        if(mp[a]==mp[b]){
            ans++;
            
        }
    }
    cout<<ans<<endl;
    
}
int main(){
    int t;
    t=1;
    while(t--){
        solve();

    }
    return 0;
}

G正方形泳池

题目链接
给定一个 N×N的方格矩阵。
左上角方格坐标为 (1,1),右下角方格坐标为 (N,N)。
有 T个方格内有树,这些方格的具体坐标已知。
我们希望建立一个正方形的泳池。
你的任务是找到一个尽可能大的正方形子矩阵,要求子矩阵内没有包含树的方格。
输出满足条件的子矩阵的最大可能边长。
输入格式
第一行包含整数 N。
第二行包含整数 T。
接下来 T行,每行包含两个整数 r,c,表示方格 (r,c)内有树。
输入保证,这 T个方格坐标两两不同。
输出格式
一个整数,表示满足条件的子矩阵的最大可能边长。
image

G思路:

看到这个题大概率是一个模拟,我们只需要横向和竖向分别两两比较,比较出最大的边长即可,但是有一个问题,我们是根据树的位置找游泳池的边长的,但是有可能最大边长并不在这些树中间,有可能在某个顶点处,所以为了全部判断,我们可以默认将四个角上种上树,这样就可以全图判断。

G代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=110;
struct node
{
    int x,y;
}a[N];
bool cmp(node a,node b){
    return a.x<b.x;

}
bool cmp1(node a,node b){
    return a.y<b.y;
}
void solve(){
    int n;
    cin>>n;
    int t;
    n+=1;
    
    cin>>t;
    for(int i=1;i<=t;i++){
        cin>>a[i].x>>a[i].y;
    }
    a[++t]={0,0};
    a[++t]={0,n};
    a[++t]={n,0};
    a[++t]={n,n};
    //将四个角加上树,这样就可以判断全图
    int ans=0;
    
    sort(a+1,a+t+1,cmp);

    for(int i=1;i<=t;i++){
        int minn=1;
        int maxn=n;
        for(int j=i+1;j<=t;j++){
            if(maxn-minn-1<a[j].x-a[i].x-1){
                //如果y轴方向的长度小于x轴方向的长度就不需要判断了(排好序)
                break;
            }
            ans=max(ans,a[j].x-a[i].x-1);
            if(a[j].y<=a[i].y){
                minn=max(minn,a[j].y);
            }
            if(a[j].y>=a[i].y){
                maxn=min(maxn,a[j].y);
            }
        }

    }
    sort(a+1,a+t+1,cmp1);

    for(int i=1;i<=t;i++){
        int minn=1;
        int maxn=n;
        for(int j=i+1;j<=t;j++){
            if(maxn-minn-1<a[j].y-a[i].y-1){
                //如果y轴方向的长度小于x轴方向的长度就不需要判断了(排好序)
                break;
            }
            ans=max(ans,a[j].y-a[i].y-1);
            if(a[j].x<=a[i].x){
                minn=max(minn,a[j].x);
            }
            if(a[j].x>=a[i].x){
                maxn=min(maxn,a[j].x);
            }
        }

    }
    cout<<ans<<endl;
    

}
int main(){
    int t;
    t=1;
    while(t--){
        solve();

    }
    return 0;
}

H好四和好五

原题链接
给定一个正整数 N,请你计算一共有多少个不同的可重复正整数集合满足:
集合中只包含 4和 5。
集合中所有元素之和恰好等于 N。
例如,当 N=14时,满足条件的集合只有一个:{4,5,5};当 N=20时,满足条件的集合有两{4,4,4,4,4}和 {5,5,5,5};当 N=40时,满足条件的集合有三个:{4,4,4,4,4,4,4,4,4,4}、{4,4,4,4,4,5,5,5,5}以及 {5,5,5,5,5,5,5,5}。
输入格式
一个正整数 N。
输出格式
一个整数,表示满足条件的集合数量。
数据范围
1≤N≤10^6

H思路+代码:

其实这个题可以枚举着做,因为n的最大情况是10的六次方,所以可以用枚举的方法,我们枚举四的倍数,如果剩下的值可以被5整除,则就是一种方案,反之也可以。

#include<bits/stdc++.h>
using namespace std;
void solve(){
    int n;
    cin>>n;
    int res=0;
    for(int i=0;i<=n/5;i++){
        if((n-i*5)%4==0){
            res++;
        }
    }
    cout<<res<<endl;
    
}
int main(){
    int t;
    t=1;
    while(t--){
        solve();
    }
    return 0;
}

然后看网上的代码,事实上这个题也可以用O(1)的时间复杂度来做,这其实就是想当于求4x+5y=n的等式的非负整数解,可以用贝祖等式,观察4x+5y=1的一个特解是(-1,1)后得原方程特解,让x在数轴上滑动推出通解表达式后卡边界即可。

什么是贝祖等式呢?

贝祖等式其实就是裴蜀定理。

定义:

若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都是d的倍数,特别的,一定存在整数x,y使得ax+by=d成立。

推论:

a,b互质的充要条件就是存在x,y使得ax+by=1.

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

void solve(){
    ll n;
    cin>>n;
    ll x=ceil((double)n/5);
    ll y=floor((double)n/4);
    cout<<y-x+1<<endl;
    return ;
    
}
int main(){
    int t;
    t=1;
    while(t--){
        solve();
    }
    return 0;
}

I 街灯

现在是基督降临节,在长为 N米的街道上有 M个街灯。
每个灯照亮了左边 K米,右边 K米。
也就是说,在 X米处的灯,能照亮从 X−K到 X+K(含)。
当然,街道某处可能被多个灯照亮。
所有灯位于不同的位置。
问题在于有可能这些灯没法照亮整条街道。
你的任务是,确定最少还要加多少灯,使得整条街道都被照亮。
输入格式
输入包含多组测试数据。
每组数据第一行包含三个整数 N,M,K.
第二行包含 M个升序的整数,表示每个灯的位置。
输出格式
每组数据输出一行,一个整数,表示答案。
数据范围
1≤N≤1000,
1≤M≤N,
0≤K≤N,
输入最多包含 100组数据。

I思路:

这题有个坑就是,不是要照亮0-n这个线段,而是照亮0-n这么多个点既可以了,我一开始还以为是自己代码写错了,结果发现是理解错题意了,那这题也不算很难,看给的数据其实模拟就可以做了。

I代码:

#include <stdio.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,k;
const int N=1010;
int p[N];

int up(int d,int p){  // d/p 上取整 
    if(d%p==0) return d/p;
    else return d/p+1;
}

int main(){
    while(cin>>n>>m>>k){
        int res=0;
        for(int i=1;i<=m;i++)
            cin>>p[i];
        for(int i=1;i<=m;i++){
            if(k==0){
                res=n-m;
                break;
            }else{
                if(i==1 && p[i]-k>1){
                    int d=p[i]-k-1;
                    res+=up(d,2*k+1);
                    // cout<<i<<": "<<up(d,2*k+1)<<endl;
                }
                if(1<i&&i<=m && p[i]-p[i-1]>2*k+1) {
                    int d=p[i]-p[i-1]-2*k-1;
                    res+=up(d,2*k+1);
                    // cout<<i<<": "<<up(d,2*k+1)<<endl;
                }
                if(i==m && n-p[i]-k>0){
                    int d=n-p[i]-k;
                    res+=up(d,2*k+1);
                    // cout<<i<<": "<<up(d,2*k+1)<<endl;
                }   
            }    
        } 
        cout<<res<<endl;
    }
    return 0;
}

J整理书籍

书架上有若干本书排成一排。
每本书要么是大型书(用 L 表示),要么是中型书(用 M 表示),要么是小型书(用 S 表示)。
我们希望所有书能够从大到小有序排列,也就是说,所有大型书都在左侧,所有中型书都在中间,所有小型书都在右侧。
为此,你可以进行任意次交换操作,每次可以任选两本书并交换它们的位置。
请你计算,为了让所有书按要求有序排列,至少需要进行多少次交换操作。
输入格式
共一行,包含一个由 L、M、S 构成的字符串,表示初始时每个位置上的书的类型。
输出格式
一个整数,表示所需要的最少交换操作次数。
数据范围
输入字符串的长度范围 [1,5×10^5]。

J思路:

这个题可以看成是一个比较简单的模拟,三种类型的书籍我们只需要排好其中两种第三种也就自然而然的排好了,根据题目的要求我们知道大书全部在左边,中型书全部在中间,小型书全部在右边,我们可以排好中型和大型书,小型书自然而然就排好了,在排序的过程中呢我们要小心大型书和中型书一交换刚好就是自己所在位置的情况,因为这样只算做是一种方案。

J代码:

#include<bits/stdc++.h>
using namespace std;
void solve(){
    string a;
    cin>>a;
    int l=count(a.begin(),a.end(),'L');
    int m=count(a.begin(),a.end(),'M');
    //当我们排好两种的时候另一种也就自然而然的排好了
    int ans1=0;
    for(int i=0;i<l;i++){
        if(a[i]!='L'){
            ans1++;
        }
    }
    int ans2=0;
    for(int i=l;i<l+m;i++){
        if(a[i]!='M'){
            ans2++;
        }
    }
    int ans3=0;
    for(int i=0;i<l;i++){
        if(a[i]=='M'){
            ans3++;
        }
    }
    int ans4=0;
    for(int i=l;i<l+m;i++){
        if(a[i]=='L'){
            ans4++;
        }
    }
    cout<<ans1+ans2-min(ans3,ans4)<<endl;

}
int main(){
    int t;
    t=1;
    while(t--){
        solve();

    }
    return 0;
}

K现代艺术

给定一个 M行 N列的方格矩阵,行从上到下依次编号为 1∼M,列从左到右依次编号为 1∼N。
初始时,所有方格都是黑色的。
一个艺术家将对矩阵依次进行 K次涂鸦操作,操作分为以下两种:
R i,表示将第 i行的所有方格改变颜色。
C j,表示将第 j列的所有方格改变颜色。
变色规则:黑色方格改变颜色会变成金色方格,金色方格改变颜色会变成黑色方格。
请你计算在所有操作完成以后,矩阵中有多少金色方格。
输入格式
第一行包含整数 M。
第二行包含整数 N。
第三行包含整数 K。
接下来 K行,每行包含一个操作指令,格式如题面描述。
输出格式
一个整数,表示金色方格的数量。
数据范围
1≤M×N≤5×106,
1≤K≤106。
1≤i≤M,
1≤j≤N。

K思路:

哈哈哈哈这个题太熟悉了,暑假牛客比赛有一个和这个题好类似,当时做了好久才做出来,知道那个怎么做之后现在做这个也就特别简单了,哈哈哈哈,模拟就可以解决了

K代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e6+10;

typedef long long ll;
int r[N];
int l[N];
//存储行和列的信息

void solve(){
    int n,m,k;
    cin>>n>>m>>k;
    memset(r,0,sizeof r);
    memset(l,0,sizeof l);
    while(k--){
        char op;
        int x;
        cin>>op;
        cin>>x;
        if(op=='R'){
            r[x]++;
        }
        else{
            l[x]++;
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if((r[i]+l[j])%2==1){
                ans++;
            }
        }
    }
    cout<<ans<<endl;
    return ;
    

}
int main(){
    int t;
    t=1;
    while(t--){
        solve();
    }
    return 0;
}

L午餐音乐会

一维数轴上站着 N个人,编号 1∼N。
初始时,第 i个人位于整数坐标位置 Pi,此人移动 1单位距离所需的成本为 Wi,他能听到与他相距不超过 Di的所有位置发出的声音。
不同的人的位置可以重叠。
现在,我们需要选择一个整数坐标位置,并在此位置举办一场音乐会。
没有人想要错过这场音乐会,所以音乐会开始后,所有听不到音乐的人都会朝音乐会举办位置方向移动,直到移动至可以听到音乐的位置为止。
我们希望合理选择音乐会的举办位置,使得所有人的移动总成本尽可能小。
请你输出这个总成本的最小可能值。
输入格式
第一行包含一个整数 N。
接下来 N行,每行包含三个整数 Pi,Wi,Di。
输出格式
一个整数,表示最小总成本。
数据范围
1≤N≤2×10^5,
0≤Pi≤10^9,
1≤Wi≤1000,
0≤Di≤10^9

L思路:

因为是要求总成本的最小可能值,看到这几个字我首先想到的是贪心,但是如果贪心其实是要针对每个人,而我们只要求把音乐会设置在一个位置,之后求最小值我想到的是二分,如果用二分对于这个曲线图是没有办法找到最小值的,二分只适用于直线,这就不得不提到三分这个概念了,三分就是适用于曲线求最值问题,很可惜三分没有学的很好,导致最开始并没有写对!之后会出一个二分和三分的总结。

L代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010;
int p[N], w[N], d[N];
int n;
int cost = 1e18;
int check(int x)
{
    int ans = 0;
    for (int i = 1; i <= n; i ++ )
        if (abs(x - p[i]) > d[i])
            ans += (abs(x - p[i]) - d[i]) * w[i];
    cost = min(ans, cost);//这里直接更新最最小代价
    return ans;
}
signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) 
        cin >> p[i] >> w[i] >> d[i];
    int l = -1e9, r = 1e9;
    while (l <= r)
    {
        int lmid = (2 * l + r) / 3, rmid = (l + 2 * r) / 3;
        // l = l + (r - l) / 3, r = r - (r - l) / 3;
        if (check(lmid) <= check(rmid)) r = rmid - 1;//如果左边的数小于右边,
        //那么左边更接近最小值,就可以直接更新r;
        else l = lmid + 1;
    }
    cout << cost << endl;
    return 0;
}

M日常通勤

多伦多有 N个地铁站,编号 1∼N。
车站之间一共有 W条单向人行道,沿第 i条人行道可以花费 1分钟时间从 Ai站步行至 Bi站。
可能有多条人行道连接同一对车站。
这里的地铁列车每天只有一班,列车会从 1号站出发,并按照一定的路线依次经过其它所有车站,每个车站都只经过一次。
初始时,列车的行进路线为 S1,S2,…,SN,其中 S1为 1,S2∼SN则是一个 2∼N的排列。
列车从任何站行驶至下一站的时间花费均为 1分钟。
在未来 D天,列车每天的行进路线都会发生变化。
第 i天发车前,行进路线会发生如下变化:指定两个下标 Xi和 Yi(2≤Xi,Yi≤N,Xi≠Yi),并交换 Sxi和 Syi。
不难发现,每次路线发生变化后,路线仍满足从 1号站出发,且经过每个车站一次。
注意,每次路线变化都将延续到随后的日子,即前面的变化会对后面的路线产生影响。
每天你都会在列车发车时刻从 1号站出发前往 N号站。
在未来 D天的每一天,你都希望知道自己至少需要花费多少时间才能到达 N号站。
在前往 N号站的过程中的每一分钟,你都可以做出以下决定:
乘坐地铁到下一站,前提是你目前与列车位于同一车站,且列车的行进路线还未完成。
从当前车站沿人行道步行至另一车站,前提是存在这样的人行道。
在当前车站保持不动。
注意:
你的出发时刻与列车发车时刻相同,这意味着你可以一开始就乘坐列车(如果你愿意的话)。
你可以在行程中途自由上车和下车,实现乘车和步行之间的任意切换。
请你计算并输出每一天的最小花费时间(单位:分钟)。
输入格式
第一行包含三个整数 N,W,D。
接下来 W 行,每行包含两个整数 Ai,Bi。
下一行包含 N个整数 S1,S2,…,SN。
接下来 D行,每行包含两个整数 Xi,Yi。
输出格式
共 D行,其中第 i 行输出第 i天的最小花费时间(单位:分钟)。
数据范围
3≤N≤2×105,
0≤W≤2×105,
1≤D≤2×105,
1≤Ai,Bi≤N,
Ai≠Bi,
1≤Si≤N,
2≤Xi,Yi≤N,
Xi≠Yi

M思路:

为什么现在写代码不敢写了???明明知道这个思路但是却不敢写就是怕出错,必须跳出自己的舒适圈!
这个题的思路:
因为这个题可以步行也可以坐地铁,首先我们需要对步行图进行简单的处理,我们要求的是到n点的距离,事实上我们可以建造一个反图,这样就可以统计每个点到n点的距离。之后我们再根据题意做一个优先队列,始终将到达n点距离最短的排在前面优先输出即可。

M代码:

下面是大佬的代码:(本人代码超时了,还在查找)

#include<bits/stdc++.h>

#define x first
#define y second

using namespace std;
const int N = 2e5+10, inf = 0x3f3f3f3f;
typedef pair<int, int> PII;

int n, w, d;
vector<vector<int>> nxt(N);
int serial[N];
int disTrain[N], disWalk[N];

void bfs() {
    memset(disWalk, 0x3f, sizeof disWalk);

    disWalk[n] = 0;
    queue<int> que;
    que.push(n);
    while(que.size()) {
        auto f = que.front();
        que.pop();
        for(auto i : nxt[f]) {
            if(disWalk[i] > disWalk[f] + 1) {
                disWalk[i] = disWalk[f] + 1;
                que.push(i);
            }
        }
    }
}

int minDis(int x) {
    int cost1 = disTrain[x] + disWalk[x];
    int cost2 = disWalk[1];
    return min(cost1, cost2);
}

int main() {
    scanf("%d%d%d", &n, &w, &d);

    while(w --) {
        int x, y;
        scanf("%d%d", &x, &y);
        nxt[y].push_back(x);
    }

    for(int i = 1; i <= n; i ++) {
        scanf("%d", &serial[i]);
        disTrain[serial[i]] = i - 1;
    }

    bfs();

    priority_queue<PII, vector<PII>, greater<PII>> heap;

    for(int i = 1; i <= n; i ++) {
        int cost = minDis(i);
        heap.push({cost, i});
    }

    while(d --) {
        int i, j;
        scanf("%d%d", &i, &j);
        int a = serial[i], b = serial[j];
        swap(serial[i], serial[j]);
        swap(disTrain[a], disTrain[b]);
        heap.push({minDis(a), a});
        heap.push({minDis(b), b});

        while(true) {
            auto t = heap.top();
            int dis = t.x, x = t.y;
            if(minDis(x) != dis) heap.pop();
            else {
                printf("%d\n", dis);
                break;
            }
        }
    }

    return 0;
}

N 密室逃脱

给定一个 M行 N列的方格矩阵,行从上到下依次编号为 1∼M,列从左到右依次编号为 1∼N。
第 r行第 j列的方格用 (r,c)表示。
你需要从方格 (1,1)出发前往方格 (M,N)。
在矩阵中的行进规则如下:
每个方格都有一个权值,当你位于权值为 x的方格时,你可以跳转到满足 a×b=x的任何方格 (a,b)。
例如,当你位于权值为 6的方格时,你可以跳转到方格 (2,3)、(3,2)、(1,6)或 (6,1)。
注意,跳跃时不能跳到界外或不存在的方格中。
例如,当矩阵一共只有 5行时,你不可能跳转到方格 (6,1)。
请你判断,你是否能够从方格 (1,1)出发并顺利到达方格 (M,N)。
输入格式
第一行包含整数 M。
第二行包含整数 N。
接下来 M行,每行包含 N个整数,其中第 r行第 c列的整数表示方格 (r,c)的权值。
输出格式
如果从方格 (1,1)出发可以顺利到达方格 (M,N),则输出 yes,否则输出 no。
数据范围
1≤M,N≤1000,
方格权值的取值范围 [1,10^6]。

N思路:

思考:如果我们想要从(1,1)走到(m,n)是不是就意味着有一条路径是可以从左上角走到右下角的,根据他产生路径的方式就是方块上的值和方块的位置有关系,我们可以选择一个搜索方式来走到右下角,怎么走呢?BFS或者是DFS,这里我优先考虑BFS,比较好写,有联系的无非就是权值和坐标的关系,所以我们就可以按照权值来存储下标,这里可以定义一个结构体,之后再进行BFS就行,当然如果整张地图都没出现m * n的权值,那就意味着永远也不会到达

N代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1010;
const int maxn=1e6+10;
struct node
{
    int x,y;
};
std::vector<node> v[maxn];
int mp[N][N];
bool f[N][N];
int n,m;
bool bfs(int x,int y){
    queue<node> q;
    q.push({x,y});
    f[x][y]=true;
    while(!q.empty()){
        auto i=q.front();
        q.pop();
        int c=mp[i.x][i.y];
        for(int i=0;i<v[c].size();i++){
            auto it=v[c][i];
            if(f[it.x][it.y]){
                continue;
            }
            if(it.x==n&&it.y==m){
                return true;
            }
            q.push(it);
            f[it.x][it.y]=true;
        }
    }
    return false;
}
void solve(){
 
    cin>>n>>m;
    bool flag=false;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>mp[i][j];
            if(n*m==mp[i][j]){
                flag=true;
            }
        }
    }
    if(!flag){
        cout<<"no"<<endl;
        return ;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            v[i*j].push_back({i,j});

        }
    }
    flag=bfs(1,1);
    
    if(flag){
        cout<<"yes"<<endl;
    }
    else{
        cout<<"no"<<endl;
    }
    return ;

}
int main(){
    int t;
    t=1;
    while(t--){
        solve();
    }
    return 0;
}

O 猜测短跑运动员的速度

一个短跑运动员在一个数轴上跑步。
他的奔跑速度是恒定的,但是奔跑方向可能会不断发生改变,有时朝数轴正方向,有时朝数轴负方向。
给定 N个不同时刻下他所在的位置,请你计算他的速度至少是多少。
输入格式
第一行包含整数 N。
接下来 N行,每行包含两个整数 T和 X,表示时刻 T 时,运动员位于位置 X。
注意,输入时刻两两不同,但是不一定按时间顺序给出。
输出格式
一个实数,表示运动员的最小可能速度。
输出结果与标准答案的相对误差小于 10^−5即视为正确。
数据范围
2≤N≤10^5,
0≤T≤10^9,
−109≤X≤109

O思路:

因为我们只是猜测速度,也就是说我们每个区间都能在这个速度下完成,所以我们可以按照速度排序,之后枚举每个时间差取最大值就好。

O代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
struct node 
{
   int t,x; 
}a[N];
bool cmp(node a,node b){
    return a.t<b.t;//按照时间进行排序
}
void solve(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].t>>a[i].x;

    }
    sort(a+1,a+n+1,cmp);
    double ans=0;
    for(int i=2;i<=n;i++){
        ans=max(ans,fabs(a[i].x-a[i-1].x)/(a[i].t-a[i-1].t));

    }
    cout<<ans<<endl;
    
}
int main(){
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}
posted @ 2023-09-08 21:07  du463  阅读(108)  评论(0编辑  收藏  举报