题解与反思

做这套题最大的感触就是前几道题没有思路,题目问的都是最少或者至少操作几次,我会往贪心方面去想,怎样才能让操作次数最少,其实不然,题目的意思只是防止重复操作而已。。

班级活动

  1. 我们我编号分为两类:
  2. a[i],出现了一次,这个编号可以改或者不改。
  3. a[i], 出现了两次,这两个编号已经符合题意了,不需要再修改。
  4. a[i], 出现大于两次,这个编号的前两个不需要修改,后边剩下的cnt - 2 个一定要修改,因为之前的a[i]编号已经不能使用。
  5. 所以我们统计出现一次的编号数量sum1, 出现大于等于两次的编号-2,剩余的编号数sum2, sum1 和 sum2 都是要修改的。如果sum1 >= sum2, 我们可以先把,sum2 的改为和 sum1 中的编号配对,sum1 剩下的编号再两两配对, res += sum2 + (sum1 - sum2)/2; 如果sum2 >= sum1, 我们只需要把sum2 的编号与sum1中的编号配对,剩下的再改为其他的即可,res += sum2;
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e5 + 10;
int st[N], sum[N];
vector<int> a;
signed main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++)
    {
        int x;
        cin >> x;
        sum[x] ++;
    }
    int s1 = 0, s2 = 0;
    for(int i = 1; i <= n; i ++)
    {
        if(sum[i] >= 2) s1 += sum[i] - 2;
        else s2 += sum[i];
    }
    if(s1 >= s2)
    {
        cout << s1 << endl;
    }
    else
    {
        cout << s1 + (s2 - s1) / 2 << endl;
    }
    return 0;
}

合并序列

  1. 因为最后的结果是两个序列一模一样,所以我们在遍历到两个数组不一样的时候,其中较小的那个数组一定要进行合并操作,使得和另一个数组相同。
  2. 所以我们可以用队列或者其他数据结构来实现这种操做。
  3. 看了一个大佬写的题解用的是前缀和 + 二分的思想,每遍历到前缀和不同的时候,我们就二分找一下,前缀和相同的在哪里,合并的次数就是l - i(j);
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e5 + 10;
int a[N], b[N];
int sum1[N], sum2[N];
signed main()
{

    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) cin >> a[i], sum1[i] = sum1[i - 1] + a[i];
    for(int i = 1; i <= m; i ++) cin >> b[i], sum2[i] = sum2[i - 1] + b[i];
    int res = 0;
    for(int i = 1, j = 1; i <= n && j <= m; )
    {
        //cout << sum1[i] << " " << sum2[j] << endl;
        if(sum1[i] == sum2[j])
        {
            i ++, j ++;
        }
        else if(sum1[i] > sum2[j])
        {
            //对sum2进行合并
            int x = sum1[i];
            int l = j, r = m;
            while(l < r)
            {
                int mid = (l + r) / 2;
                if(sum2[mid] >= x) r = mid;
                else l = mid + 1;
            }
            res += l - j;
            //cout << l << " " << j << endl;
            j = l;
            //res += j - i;
        }
        else
        {
            int l = i, r = n;
            int x = sum2[j];
            while(l < r)
            {
                int mid = (l + r) / 2;
                if(sum1[mid] >= x) r = mid;
                else l = mid + 1;
            }
            res += l - i;
            i = l;
        }
    }
    cout << res << endl;
    return 0;
}

数三角

  1. 直接暴力枚举任意三个点,判断能否组成合法的等腰三角形即可,可以拿20分。
  2. 我们发现枚举的有些点是没有意义的,对于某个点来说,要组成等腰三角形,那么另外两个点到它的距离应该是一样的,所以我们可以记录一下到每个点距离相同的点有哪些,然后从这些点里面选两个点,判断三点不在一条线上就可以构成合法的等腰三角形。
  3. 我们可以用一个map<int, vector> ma; doule是到这个点的距离为d,vetor存距离为d的点有哪些,然后我们可以从这里面选。
  4. 如何判断三点是否共线和,我们可以用向量的思想:AB = (x1,y1), CD = (x2,y2) 如果两者平行有,x1 * y2 = x2 * y1, 当有一个公共点是及共线。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2005;
// 存储每个点
struct node
{
    int x, y;

} p[N];
// 计算两个点距离的平方,开根号会影响进度,只要判断相等即可所以不开根号也没问题
int dist(int i, int j)
{
    int res = (p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) *(p[i].y - p[j].y);
    return res;
}
bool check(int i, int j, int k)
{
    //判断三点是否共线,向量交叉相乘
    int d1 = (p[j].x - p[i].x) * (p[k].y - p[i].y);
    int d2 = (p[k].x - p[i].x) * (p[j].y - p[i].y);
    if(d1 != d2) return true;
    return false;
}
signed main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++)
    {
        cin >> p[i].x >> p[i].y;
    }
    int res = 0;
    for(int i = 0; i < n; i ++)
    {
        map<int, vector<int>> mp;
        for(int j = 0; j < n; j ++)
        {
            if(i == j) continue;
            int d = dist(i, j);
            mp[d].push_back(j);
        }
        
        for(auto it : mp)
        {
            int n = it.second.size();
            auto v = it.second;
            for(int ii = 0; ii < n; ii ++)
            {
                for(int jj = ii + 1; jj < n; jj ++)
                {
                    //从vector中拿出两个点,和顶点判断一下是否共线
                    int pp1 = v[ii];
                    int pp2 = v[jj];
                    if(check(i, pp1, pp2)) res ++;
                }
            }
        }
    }
    cout << res << endl;
    return 0;
}

AB路线

  1. 刚开始以为就是一个简单的bfs(), 后来看了题解确实是bfs(), 但是不同的是,每个点可以重复访问,而不是访问一次就结束了,因为每个A或B要走k次,如果图中的A或B的数量小于k,那么我们肯定要重复走的。
  2. 重复走,我们应该限制不能走重复的路径,所以st[i][j][k]可以加一维,表示我们 走到i,j, 经过了几个重复的A或B,如果st[i][j][k], 之前已经出现过了,我们就不要在进行这种情况了,不然会重复计算这样路径肯定会更大。
  3. 我们在进行bfs()时候,嵌套了两层,其实就是我们用当前的结点更新完所有可到达的结点后,再把res++, 这样res 就是最后的答案。
#include<bits/stdc++.h>
#define int long long
using namespace std;

struct node
{
    int x, y, step;
    //step,表示到达(x,y), 经过了几个连续的A或B
};
const int N = 1005;
int st[N][N][12];
int dx[] = {0,0, 1,-1};
int dy[] = {1,-1,0,0};
string s[N];
int res,n,m,k;
int bfs()
{
    queue<node> q;
    q.push({0, 0, 1});
    st[0][0][1] = 1;
    while(q.size())
    {
        int len = q.size();
        while(len --)
        {
            auto h = q.front();
            q.pop();
            int x = h.x, y = h.y, step = h.step;
            if(x == n - 1 && y == m - 1)
            {
                return res;
            }
            for(int i = 0; i < 4; i ++)
            {
                int nex = x + dx[i];
                int ney = y + dy[i];
                
                if(nex < 0 || ney < 0 || nex >= n || ney >= m) continue;//越界
                if(step < k)
                {
                    //连续的个数<k, 应该走一样的
                    if(s[nex][ney] == s[x][y] && st[nex][ney][step + 1] == 0)
                    {
                        st[nex][ney][step + 1] = 1;
                        q.push({nex, ney, step + 1});
                    }
                }
                else
                {
                    //连续的个数==k, 应该走不一样的
                    if(s[nex][ney] != s[x][y] && st[nex][ney][1] == 0)
                    {
                        st[nex][ney][1] = 1;
                        q.push({nex, ney, 1});
                    }
                }
            }
        }
        res ++;
    }
    return -1;
}
signed main()
{

    cin >> n >> m >> k;
    for(int i = 0; i < n; i ++)
    {
        cin >> s[i];
    }
    cout << bfs();
    return 0;
}
               

抓娃娃

  1. 直接暴力枚举每个线段时候被包含在区间之中,20分。

  2. 看了大佬的思路豁然开朗,因为题目里给了一个条件就是区间的长度一定大于等于线段的长度,所以就不会出现线段包含区间的情况了。

  3. 要求区间要包含线段至少二分之一,我们可以画一下符合条件的情况找一下规律: alt

  4. 我们发现,只要线段的终点在区间里面,就可以保证区间包含线段至少二分之一。

  5. 基于以上思想,我们可以用sum[i],来表示在[1, i]里面包含多少个线段的终点,所以对于每个区间就是 res = sum[y] - sum[x - 1]。

  6. 算终点坐标的时候要除以2,为了避免精度损失,我们把都乘以2.

#include<bits/stdc++.h>
//#define int long long
#define LL __int128_t
using namespace std;
const int N = 2e6 + 10;
int a[N];
int sum[N], cnt[N];
//map<int, int> cnt;
//map<int, int> sum;
signed main()
{
   ios_base::sync_with_stdio(0);
   cin.tie(0), cout.tie(0);
   int n, m;
   cin >> n >> m;
   for(int i = 1; i <= n; i ++)
   {
       int x, y;
       cin >> x >> y;
       a[i] = x + y;
       cnt[x + y] ++;
   }
   for(int i = 1; i <= 2e6; i ++)
   {
       sum[i] = sum[i - 1];
       if(cnt[i] > 0) sum[i] += cnt[i];
   }
   while(m --)
   {
       int x, y;
       cin >> x >> y;
       x *= 2, y *= 2;
       int res = sum[y] - sum[x - 1];
       cout << res << endl;
   }
   return 0;
}