Codeforces Round #683 (Div. 2, by Meet IT)

A. Add Candies

大意: 给出一个数\(n\),代表有\(n\)个堆糖果且\(a_i=i\)代表每堆糖果数量,每次可以选择一堆糖果,使得除了这堆糖果以外的每一堆糖果数量都加上这堆糖果的数量,问怎样操作可以最终使得每堆糖果数量相同
思路: 构造题,每次都选第\(i\)堆即可,这样最后每一堆糖果的数量都是\(1+2+3....+n\)

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
int t, n;

int main(){
    cin>>t;
    while(t--){
        cin >> n;
        cout << n << endl;
        for (int i = 1; i <=n;i++){
            cout << i << ' ';
        }
        cout << endl;
    }
    return 0;
}

B. Numbers Box

大意: \(n*m\)的矩阵,每次操作可以选择相邻的一对数字取反,问经过多次操作后矩阵的元素和最大为多少
思路: 自己用笔试一试可以发现,可以经过多次操作将负号传递给别的数字,那么如果有偶数个负数,就一定能经过多次操作使负号都聚集到一起然后两两消掉,而如果矩阵中有\(0\),则可以通过把多次操作把负号移动到\(0\)的附近,然后通过\(0\)来消掉,而对于有奇数个负数且没有\(0\)的情况,则只需要将矩阵中绝对值最小的数变为负数,其他变为正数即可。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
int anss,flag,fsum,maxf,t,n,m,x,fnum;
int main(){
    cin>>t;
    while(t--){
        cin>>n>>m;
        anss=0;
        flag=0;
        fsum=0;
        fnum = 0;
        maxf=1e9;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                cin>>x;
                if(x==0)
                flag=1;
                if(x>0){
                    anss+=x;
                    maxf=min(maxf,abs(x));
                }
                if(x<0){
                    fsum+=x;
                    fnum +=1;
                    maxf=min(maxf,abs(x));
                }
            }
        }
        if(flag==1){
            anss+=abs(fsum);
        }
        else if(fnum%2==0){
            anss+=abs(fsum);
        }
        else{
            anss+=abs(fsum);
            anss-=2*maxf;
        }
        cout<<anss<<endl;
    }
    return 0;
}

C. Knapsack

大意:给你\(n\)件物品和一个容量为\(w\)的背包,每件物品只能装入一次
求将背包装到 \(w/2 − w\)的任意一种方案,输出物品数量和装入物品的序号
思路:因为是任意一种,所以不需要用背包去做,只需要贪心去考虑,首先将物品重量排一下序,然后先选最大的那个,如果大于\(w\)就找次大的...直到直到一个处于\([w/2,w]\)区间的数直接输出,或者找到小于\(w/2\)的数,从这个数开始,遇到一个数就加一个,直到总和大于\(w/2\)或者没有物品可加为止。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
vector< pair<int,int> >a;
long long  t, n, x,anss[200005],sum,yes;
double m;
int cnt;
int main(){
    cin>>t;
    while(t--){
        cin>>n>>m;
        cnt = 0;
        sum = 0;
        yes = 0;
        a.clear();
        for(int i=0;i<n;i++){
            cin>>x;
            a.push_back({x, i});
        }
        sort(a.begin(),a.end());
        for(int i=n-1;i>=0;i--){
            if(a[i].first>m)
                continue;
            else if((a[i].first>=ceil(m/2))&&(a[i].first<=m)){
                cout << 1 << endl;
                cout << a[i].second+1 << endl;
                yes = 1;
                break;
            }
            else{
                sum += a[i].first;
                anss[cnt++] = a[i].second+1;
                if((sum>=ceil(m/2))&&(sum<=m)){
                    cout << cnt <<endl;
                    for (int j = 0; j < cnt;j++){
                        cout << anss[j] << ' ';
                    }
                    cout << endl;
                    yes = 1;
                    break;
                }
            }
        }
        if(yes==0)
        cout << "-1" << endl;
    }
    return 0;
}

D. Catching Cheaters

大意: 规定 \(S(C,D) = 4\cdot LCS(C,D) - |C| - |D|\) ,求\(A\)和\(B\)的子串\(C\)和\(D\)的最大\(S\)值
思路: \(dp[i][j]\)代表以\(A[i]\)和\(B[j]\)为结尾的子串的\(S\)值,转移方程:

\[if(s1[i]==s2[j]): { dp[i][j] = max(dp[i - 1][j - 1],0) + 2; }\]

\[else: { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) - 1; }\]

#include<bits/stdc++.h>

using namespace std;

const int N = 5e3 + 5;

int n, m,dp[N][N],anss;
char s1[N], s2[N];

int main(){
    cin >> n >> m;
    cin >> s1 + 1 >> s2 + 1;
    anss = 0;
    dp[0][0] = 0;
    for (int i = 1; i <= n;i++){
        for (int j = 1; j <= m;j++){
            if(s1[i]==s2[j]){
                dp[i][j] = max(dp[i - 1][j - 1],0) + 2;
            }
            else{
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) - 1;
            }
            anss = max(anss, dp[i][j]);
        }
    }
    cout << anss << endl;
    return 0;
}

E. Xor Tree

大意:
给定一个长度为n的数组a,对于数组a里面的每个数字a[i],他会找到一个和他异或值最小的点j,使得i到j建立一条无向边。这样建出来的图可能是不连通图,求最少删掉几个点后能够使得按照上述规则建出来的图是一张连通图。
思路:建立01 trie树,对于每个叶子节点代表一个元素a,对于每个元素a,要求异或值最小,那么一定从其兄弟节点寻找,因为它和兄弟节点的大部分二进制表示都是相同的,所以对于一个节点的左右子树,必然是会产生两个联通块,而最后需要只剩下一个最大的连通块,所以选择较大的那个子树,然后另一个只留下1即可,然后向上返回。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
int n, m, T, a[N];
int son[N * 32][2], idx;

void insert(int num) {          //01 trie
    int p = 0;  // 从根节点开始
    for (int i = 30; i >= 0; --i) {  // 从第30位开始,前面不够要补零
        int u = (num >> i & 1);  // 判断num这一位上的数字
        if (!son[p][u]) son[p][u] = ++idx;  // 如果不存在,就建立一个结点
        p = son[p][u];  // 从父节点往下走
    }
}

int query(int x){
    int l = 0, r = 0;
    if(son[x][0]==0&&son[x][1]==0){
        return 1;  //叶子节点 返回1
    }
    if(son[x][0]){
        l = query(son[x][0]);
    }
    if(son[x][1]){
        r = query(son[x][1]);
    }
    if(son[x][0]&&son[x][1]){
        return max(l, r) + 1;  //如果左右都有子树,那么返回左右子树中较大的那个加上另一个子树中的一个点,因为要使得最后的连通图最大
    }
    else{
        return l + r; //如果左右只有一个子树,就只返回这一个子树即可
    }
}

int main(){
    cin >> n;
    for (int i = 0; i < n;i++){
        int x;
        cin >> x;
        insert(x);
    }
    cout << n - query(0) << endl;  //0是根节点,相当于从0开始往下查询了一遍
    return 0;
}

F1. Frequency Problem (Easy Version) & F2. Frequency Problem (Hard Version)

大意: 出一串数字,要求找到一个最长子区间,需要满足:这个子区间中出现最多次数的数字有不止一个。
例如:1 1 1 5 4 1 3 1 2 2
满足要求的最长区间为5 4 1 3 1 2 2,因为1和2都出现了2次
思路: 先来想朴素的算法,首先可以确定的是假设在整个数组中出现次数最多的数字为\(mx\),那么对于答案区间,\(mx\)必然也是出现次数最多的数之一。所以我们可以枚举另一个出现次数最多的数,另其为d,然后从前往后扫描整个数组,遇到d则使cur++,遇到\(mx\)则使\(cur--\),用数组\(last[cur]\) 记录cur第一次出现的位置,那么对于位置\(i\),得到一个\(cur\)值,\(i-last[cur]+1\)则是满足\(mx\)和\(d\)出现次数相等的最长区间,那么怎么找到最终的答案呢,就需要枚举\(d\),这样复杂度是\(O(n^2)\)

但是本题数据量大,不能采用\(O(n^2)\)的算法,考虑根号平衡,读入时记录\(a[i]\)在数组中出现的次数\(cnt[a[i]]\)。然后处理时,只选取\(cnt[a[i]]>=\sqrt{n}\)的数作为参数\(d\)调用上文的算法,这样这一部分的算法复杂度是\(n*\sqrt{n}\),因为符合条件的数最多有\(\sqrt{n}\)个。然后对于\(cnt[a[i]]<\sqrt{n}\)的数,直接枚举出现的次数\(t\),用尺取法更新区间长度,这样这一部分的算法复杂度也是\(n*\sqrt{n}\)

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

const int maxn = 2e5 + 10;

int n,a[maxn],cnt[maxn],mx=0,last[maxn*2],ans=0,vis[maxn]; 

void solve1(int x){
	for (int i = 1; i <= n;i++)
        last[n - i] = last[n + i] = -1;
    last[n] = 0;
    int cur=0;
	for (int i = 1;  i <= n;i++){
		if(a[i]==x)
            cur++;
        else if(a[i]==mx)
            cur--;
		if(last[n+cur]==-1)   //+n是为了防止下标为负
            last[n + cur] = i;
        else
            ans = max(ans, i - last[n + cur]);
    }
}
void solve2(int x){
	for (int i = 1; i <= n;i++)cnt[i]=0;
	int l=1,equal=0;
	cnt[a[l]]++;
    if(cnt[a[l]]==x)equal++;
	for (int i = 2; i <= n;i++){
		cnt[a[i]]++;
		if(cnt[a[i]]==x)equal++;
		else if(cnt[a[i]]>x){
			while(cnt[a[i]]>x){
				cnt[a[l]]--;
				if(cnt[a[l]]==x-1)equal--;
				l++;
			}
		}
		if(equal>=2)ans=max(ans,i-l+1);
	} 
}
int main(){
    cin >> n;
    for (int i = 1; i <= n;i++){
        cin >> a[i];
        cnt[a[i]]++;
        mx = cnt[mx] < cnt[a[i]] ? a[i] : mx;
    }
	int limt=sqrt(n);
	for (int i = 1; i <= n;i++){
        if(cnt[i]>=limt && i!=mx&&vis[i]==0){
            solve1(i);
            vis[i] = 1;
        }
    }
	for (int i = 1; i < limt;i++)solve2(i);
    cout << ans << endl;
}
上一篇:Codeforces Round #683 (Div. 2, by Meet IT) C


下一篇:redis之通信开销限制redis cluster规模的关键因素