简单思维题

(感觉写博客时间比写题时间长,很难受。。

写代码之前:

第一步读题面和数据范围,首要的是准确,其次才是速度

第二步看解释和样例加深理解题目想思路,先考虑找规律猜结论/无解情况/特殊情况,实在想不出看看过题人数估算代码量

别忘了开LL

目录

一、tricks

1. 计算范围避免超时

B. Diverse Substrings

2. 贪心

C. Bricks and Bags

3. 空间换时间

4. 正难则反

C.Monoblock

5. 求贡献

①区间操作题:

②数论题:

③位运算题:

7. 构造

8. gcd与lcm

B. Marin and Anti-coprime Permutation

A. Bestie

9. 位运算

D. Yet Another Problem


一、tricks

1. 计算范围避免超时

B. Diverse Substrings

题意:求给出的字符串的所有子串满足diverse的有多少个(该串中每个数字的出现次数都不超过整条串数字种类数)t<=1e4, sum of n<=1e5

思路:一眼暴力。对于符合条件的串,由于0~9一共10种数字,每种数字最多10个,所以最长长度为100,所以时间复杂度O(100n)

2. 贪心

C. Bricks and Bags

题意:(来回读了三遍才明白..文化沙漠)

Pak在1,2,3号背包分装总数为n个的石头堆,每个石头重量ai,不能有空包;Bu在三个包里分别取个石头,从i号包取出的石头重量为wi。总分数定义为|w1−w2|+|w2−w3|,Bu取出时会使总分数最小化,求Pak分装采取最优策略的最大可能分数。

思路:写的很nb的dalao题解,不贴我的了C. Bricks and Bags Codeforces Round #831 (Div. 1 + Div. 2)_jikelk的博客-CSDN博客

#include
using namespace std;
//#define int long long
const int N=2e5+10;
int a[N];
typedef long long ll;

 void solve(){
    int n;scanf("%d",&n);
    for(int i=0;i=2;i--)
        ans=max(ans,2*a[i]-a[i-1]-a[0]);

    cout<>T;
	while(T--){
        solve();
	}

}

3. 空间换时间

除了dp以外,见过的一般用法是将计算结果和正确结果分别储存最后两者进行比较

4. 正难则反

一般是通过已知的等量关系将所求转化成差(已知量-更容易求得的)或者商

C.Monoblock

题意:定义一段连续的只有一种数字的最长子段为block,一个区间的含有block数为g(l,r)。长为n的序列中,m次操作每次给出i和x,把a[i]变为x,请你输出每次操作后所有子区间的g。(1<=n,m<=1e5, 1s) O(n+m)

思路:利用将所求转换为求差值,然后统计邻项贡献求解。

g(l,r) = 该区间长度 -  Σ与前一个数字相同的数的个数

对于a[i],a[i-1],如果邻项相同,则对包含这两个数区间的贡献为包含这两个数的区间数量,即(n-i+1)(i-1),因为这种子区间的左端点可取到前i-1个点,右端点可取后n-i+1个;如果邻项不同,则对包含的区间的贡献为0(所以官方题解中是找“分界点”计算的)。

综上所述,所求即为Σ所有子区间长度-Σ各区间对应贡献。

那么对于整道题,首先对初始序列进行一次计算,后每次询问先将其原贡献去除,再把新贡献带入计算。

#include 
using namespace std;
#define int ll
const int N=1e5+10;
int n, m;
int a[N];
 
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
 
    int ans = 0, now = 0;
 
    for(int i=1;i<=n;i++){
        if (a[i] != a[i-1])
            now += i;
        else now++;
 
        ans+=now;
    }
 
 
    int i, x;
    while(m--){
        cin>>i>>x;
        if (a[i-1] != a[i] && a[i-1] == x)
            ans-=(i-1)*(n-i+1);
 
        if (a[i-1] == a[i] && a[i-1] != x)
            ans+=(i-1)*(n-i+1);
 
        if (a[i+1] != a[i] && a[i+1] == x)
            ans-=i*(n-i);
 
        if (a[i+1] == a[i] && a[i+1] != x)
            ans+=i*(n-i);
 
        a[i] = x;
 
        cout<>T;
    //while(T--)
        solve();
}

5. 求贡献

①区间操作题:

C.Monoblock (见上)

②数论题:

D. Make It Round

题意:已知n和m,要求n*k(1<=k<=m)结尾0个数最多,求最大的n*k

思路:首先考虑如何让0尽可能多。从因数角度,10=1*10=2*5,所以只需要考虑2与5的贡献。

如果因数5的个数>2的个数则凑更多的2,反之凑更多5,剩下的凑10

其次考虑如何求最大。k*n*x不会改变结尾0的个数,那么让x取最大值x(max) =m/x*x,所以答案就是n∗m / x*x

#include 
using namespace std;
#define int long long
const int N=1e3+10;
int n,m,cnt1,cnt2;

void solve(){
	cnt1=0,cnt2=0;
	cin>>n>>m;
	int t=n;
	while(n%2==0){
		cnt1++;
		n/=2;
	}
	while(n%5==0){
		cnt2++;
		n/=5;
	}
	int k=1;
	while(cnt1>T;
	while(T--)solve();
	
}

D. Divisiblility by 2^n

题意:对给定数组每次操作选择一个i,使得ai=ai*i,每个i只能选择一次。求最少多少次操作之后数组元素的乘积为2^n的倍数,无解则-1。

思路:考虑贡献。先处理出数组2的因子个数,若一开始因子个数>=n,那么不需要进行任何操作,输出0;若因子个数

#include 
using namespace std;
#define int long long
const int N=2e5+10;
vector > v;int n,sum,sum2;int a[N];

bool cmp(pair a,pair b){
	return a.second>b.second;
}

void solve(){
	sum=0,sum2=0;
	v.clear();
	cin>>n;
	for(int i=1;i<=n;i++){
		sum2=0;
		cin>>a[i];
		int tmp=a[i];

		while(tmp%2==0) 
            sum++,tmp/=2;

		tmp=i;
		while(tmp%2==0) 
            sum2++,tmp/=2;

		if(sum2) v.push_back({i,sum2});
	}

	sort(v.begin(),v.end(),cmp);

	int res=n-sum,ans=0;
	if(sum>=n) 
        cout<<0<>T;
	while(T--)solve();

}

③位运算题:

7. 构造

8. gcd与lcm

gcd常用技巧/结论:

①换个角度从gcd本身看问题,例如枚举gcd,k倍gcd...

②n个数的gcd为1=这堆数中至少有一对互质数=所有数的因子重合小于n

③构造题常用:相邻两个数gcd为1

④两个数互质=它们因子的集合之间没有交集

lcm常见结论:

①若a是质数,则a和其他任意数的lcm就是其乘积

②若a是合数,那么a和a的约数的lcm就是a

③由①②可得构造题常用如下

lcm(a,ka):使lcm最小

lcm(a,b)且ab互质:使lcm最大

B. Marin and Anti-coprime Permutation

题意:已知n求有多少个n的排列满足gcd(1*p1,2*p2...n*pn)>1

思路:从gcd的角度考虑,假设gcd=k则这n个数中包含k这个因子的数有n/k个,加上要乘的数应有2*n/k个保证2*n/k>=n只能是k=2,所以就让所有的数相乘为偶数。若n为奇数结果为0,偶数则为(n/2)!*(n/2)!

以上是证明,但是实际比赛是直接猜的

A. Bestie

题意:长为n的数组中,将任意的ai变成gcd(ai,i),需要花费n-i+1,求将数组所有数的gcd变为1需要的最小费用

思路:首先求出所有数的gcd,记为gc,如果gc等于1就直接输出0,否则从后往前看,因为后面的数费用更小。同时因为相邻两个数的gcd等于1,所以我们如果对后两个数各进行一次操作,无论操作后的结果是什么,这两个数的gcd都是1,只要有两个数的gcd=1,gc=1,所以答案的最大值就是3。随后观察发现如果ai和i的gcd为1,那么n-i+1就是一种可行的方案。同时我们可以不用遍历每个数,可以判断gc和1到n中的某个数gcd为1,那么答案就是n-某个数+1,因为gc和某个数gcd为1,则必然又一个ai满足gcd(ai,i)=1
 

#include
using namespace std;
int a[25];
void solve(){
		int n;scanf("%d", &n);
		int gc;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			if (i == 1)
				gc = a[i];
			if (i >= 2)
				gc = __gcd(gc, a[i]);
		}
		if(gc==1){
			puts("0");
			continue;
		}
		else{
			bool flag = 0;
			for (int i = n; i >= n-1; i--)
				if (gcd(i, gc) == 1){//只需判断和后两个数的gcd是否为1
					cout<>T;
	while (t--){
        solve();
	}

}

9. 位运算

D. Maximum AND

题意:已知长为n的序列a和b,可以调整b的顺序使得c[i] = a[i] ^ b[i],求c[1]&c[2]&...&c[n]的max

思路:如果相与和的某一位为1,那么每个c[i]的这一位都为1,所以a和b中所有的数的这一位是相反的,因此可以直接高位到低位枚举30~0位(100...0,110...0,...111...1)(即|=1<  

#include 
using namespace std;
typedef long long ll;
const int N =1e5+10;
int n;
int a[N],b[N],c[N],d[N];
 
bool check(int x){
    for(int i=0;i>n;
    for(int i=0;i>a[i];
    for(int i=0;i>b[i];
 
    int ans = 0;
    for(int i=30;i>=0;i--)
    {
        int x = ans|1<>T;
    while(T--) solve();
 
}

D. Yet Another Problem

题意:长度n的数组,q询问次数,每次给区间[l,r],可以对这个区间内的一个奇数长度的子区间操作,操作后的结果是这个子区间内的所有数都变为它们的异或和,求将[l,r]全变为0的最少操作次数,否则-1

思路:首先发现一个性质,操作后并不会改变该区间的异或和。所以对于一开始给定区间内元素异或和不为0的直接输出-1。

如果给定区间内所有元素均为0那么我们就直接输出0。否则我们分区间长度的奇偶性进行讨论:

如果区间长度是奇数,那么我们直接选择整个区间进行一次操作就可以使得整个区间全变为0,所以直接输出1。如果区间长度是偶数:

先看区间的两端是否存在0,如果存在0的话,我们选择除了这个0以外的其他所有数进行一次操作即可,这样结果就是1,否则我们需要判断是否有后缀奇数长度的区间使得异或和为0(前缀也是等价的,因为偶数减去一个奇数得到一个奇数,那么后面奇数长度的异或和等于0,由于整个区间异或和等于0,说明前面的奇数长度的区间异或值也是0),那么我们可以先对这个区间进行一次操作,使得产生若干个后缀0,那么我们再对未选区间+一个0进行一次操作,这样就能够通过两次操作使得整个区间内的数全变为0了。

具体做法中,判断是否存在后缀奇数长度的区间使得异或和为0时不能暴力判断,我们可以记录一下以当前位置作为区间右边界可以满足异或值为0的最大区间左边界的位置,保证区间长度为奇数,那么我们就直接用两个map分奇数和偶数存一下。最后直接判断以r为右边界的最大左边界与l的关系。
 

#include
using namespace std;
const int N=2e5+10;
int a[N],l[N];
long long s[N],x[N];

signed main()
{
	int n,m;
	cin>>n>>m;
	mapmp[2];
	mp[0][0]=0;
	int t=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s[i]=s[i-1]+a[i];
		x[i]=x[i-1]^a[i];
		t^=a[i];
		int s=(i&1)^1;
		if(mp[s].count(t))
			l[i]=mp[s][t]+1;
		mp[s^1][t]=i;
	}

	while(m--)
	{
		int ll,rr;
		scanf("%d%d",&ll,&rr);
		if(x[rr]^x[ll-1]) puts("-1");
		else
		{
			if((rr-ll+1)&1)
			{
				if(s[rr]-s[ll-1]==0) puts("0");
				else puts("1");
			}
			else
			{
				if(s[rr]-s[ll-1]==0) puts("0");
				else if(a[ll]==0||a[rr]==0) puts("1");
				else
				{
					if(l[rr]>=ll) puts("2");
					else puts("-1");
				}
			}
		}
	}

}

你可能感兴趣的:(算法)