两个整数n和k(有一个坑点是它括号里写了n,k的大小顺序,然后把k放前面,然后就以为先输入k)
长度为n的序列
美丽序列:所有长度为k的连续子序列和相同
插入若干整数(1到n)使得序列变成美丽序列,无解输出-1
数据比较小,直接暴力枚举
题目中说如果存在的话,长度不超过1e4,那么肯定是通过暴力不断插入
有一种通用的万能的构造方法,就是一直在每组k的后面插入,然后维护前面的全满足
比如 1 2 3 4 ,k=3
那么要满足第二组k个,那么就要在3后面插个1,同理,依次需要按顺序插入2,3,4然后如果要插入的数就是待插的数那么很好,如果没有就要自己插入新的数,直到最后一个待插入的数插进去,题目中说了如果存在的话,那么长度不超过1e4,那么当长度超过1e4时若还没有插完,那么无解
事实证明这种构造方法并不对,因为如果待插入的数在前面没出现过的话,那么永远也插不进去
可以发现,如果k为4的话,那么必须刚好4种数字,然后一直循环,这样才能满足题意,如果数字种数超了k个,那么无解,否则可以通过补数和循环,循环次数的话可以用10000/数字种数,因为题目数据保证了长度最大10000,所以可以直接让长度最大,那么肯定保证循环次数足够多
坑点:括号里写了n,k的大小顺序,然后把k放前面,然后就以为先输入k
trick:
1.自以为想出了一种万能的通用的构造方法,实际是错的,避免方法是先通过样例检验,然后自己再造一些极端的数据来验证
2.如果一个数组中所有长度为 k的子数组的和都相同,那么这个数组就是美丽的。数组的子数组是任何连续元素的序列----这是美丽数组的性质,我们可以通过手玩举一些例子来挖掘其背后的性质:数字种数必须小于等于k,然后不断循环,这样才能满足性质,这一性质的挖掘是这题解题的关键
3.题目说了构造长度不超过10000,那么我们不用考虑多长,就直接长度最大化,接近10000,这样就不用考虑要多长了
4.数据比较小,暴力是一个很好的方向
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
int a[N];
int n,k;
void solve() {
cin>>n>>k;
set<int>s;
map<int,int>mp;
for(int i=1;i<=n;i++) cin>>a[i],s.insert(a[i]),mp[a[i]]++;
int maxn=0;
for(auto v:mp){
maxn=max(maxn,v.second);
}
if((int)s.size()>k){
cout<<-1<<endl;
return;
}
int len=s.size();
vector<int>ans;
while(s.size()){
ans.push_back(*s.begin());
s.erase(s.begin());
}
for(int i=0;i<k-len;i++) ans.push_back(1);
cout<<10000/(int)ans.size()*(int)ans.size()<<endl;
for(int i=1;i<=10000/(int)ans.size();i++){
for(auto v:ans) cout<<v<<' ';
}
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的a序列和b序列是全排列
操作:将序列循环右移或左移
操作不限次数
求最大匹配元素对数量
匹配:大小和位置均相同
只要移动一个序列即可
最—贪心,一边遍历,一边贪,不可行
猜一下:让第一个数匹配,然后数匹配总数,不可行,因为可能虽然第一个不匹配,但是其它匹配了很多
挖掘操作背后的性质,什么是一定的,什么是不变的—想不到,感觉就算想挖掘也想不出,这作为其中一个思考的方向,当然想不出也没办法,也很正常,因为一个题目在被出出来的时候,基本解题方向已经被限死了,很小范围,所以这个方向想不出是很正常的,作为一个可能的方向,想不出就换方向
统计每个b i需要往右移多少位和a j 对齐(b i ==a j )然 后 看 看 往 右 移 动 几 次 对 答 案 最 多
trick:
1.反向思考,先按照正常的朴素的正向思维想(往往不饶弯子),比如这题就是每循环右移一次(0到n次),求出匹配的数量,然后取最大匹配数量即可,但是这样肯定超时了,所以反过来思考(转一次弯),将每个字符匹配累加到循环右移的次数上,这样利用一个map,就不会超时了,遍历b的每个数,将每个数移到正确位置的次数++,比如将3移到正确的位置需要两次,将4移到正确的位置也需要两次,那么移动两次匹配数量则为2
2.挖掘操作背后的性质,什么是一定的,什么是不变的—想不到,感觉就算想挖掘也想不出,这作为其中一个思考的方向,当然想不出也没办法,也很正常,因为一个题目在被出出来的时候,基本解题方向已经被限死了,很小范围,所以这个方向想不出是很正常的,作为一个可能的方向,想不出就换方向
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N],b[N];
int n;
void solve() {
cin>>n;
map<int,int>mp,pos;
for(int i=1;i<=n;i++) cin>>a[i],pos[a[i]]=i;
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++){
if(pos[b[i]]>=i) mp[pos[b[i]]-i]++;
else{
mp[pos[b[i]]+n-i]++;
}
}
int ans=0;
for(int i=0;i<=n;i++){
ans=max(ans,mp[i]);
}
cout<<ans<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a,最初为全排列
操作:可以移除相邻的正序的两个数的其中一个
次数不限
问能否让数组长度变成1
完全升序的肯定可以
降升也可以,两降就不行
逆序对的数量(包括相等的对数)如果大于等于2,那么就NO,否则YES
坑点:有的题目YES,NO大小写不区分,有的题目区分大小写
逆序对用归并排序
逆序对错了,想出一个思路一定得用样例仔细验证,不要看一眼感觉对,代码都写出来了才发现思路错了
不要通过样例来猜做法,首先样例并不全,其次样例具有误导性,还是应该通过题目给的信息来自己手玩造样例来推测(要造那种复杂的,然后不同种类的多一些,全一些),题目所给样例只是用来验证思路的正确性的
通过手玩造复杂样例可以发现,只要第一个元素小于最后一个元素,那么YES,否则NO
坑点:
1.有的题目YES,NO大小写不区分,有的题目区分大小写
2.想出一个思路一定得用样例仔细验证,不要看一眼感觉对,代码都写出来了才发现思路错了、
trick:
手玩造复杂,不同种类的样例
不要通过样例来猜做法,首先样例并不全,其次样例具有误导性,还是应该通过题目给的信息来自己手玩造样例来推测(要造那种复杂的,然后不同种类的多一些,全一些),题目所给样例只是用来验证思路的正确性的
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=3e5+10;
int a[N];
int n;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
if(a[1]<a[n]) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a
操作:删除两个不同的元素
输出数组最少剩下几个元素
与顺序无关 ,先排个序
成对删除,如果是奇数个,至少剩一个
先把数量最多的消耗,不然它自己是消耗不了的
通过手玩发现,如果最高的那个数量大于等于其它所有之和,那么答案即为最高的减去其它之和
否则,一定可以消完,当然偶数个消成0,奇数个消成1
trick:手玩
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
cin>>n;
map<int,int>mp;
int sum=0;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
int maxn=0;
for(auto v:mp){
maxn=max(maxn,v.second);
sum+=v.second;
}
if(maxn>=sum-maxn) cout<<maxn-(sum-maxn)<<endl;
else{
if(n%2==1) cout<<1<<endl;
else cout<<0<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
一共有n张数字0,m张数字1
构造序列,使得不能有两张0相邻,不能有连续3个1
一个0配一个1或者两个1
一个0最多配两个1,然后最多最后再来两个1
一个0最少配一个1
trick:这边主要用到平均分的技巧(上次已经总结过了)
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
cin>>n>>m;
if(n>m+1||2*n+2<m){
cout<<-1<<endl;
return;
}
//特判
if(2*n+2==m){
cout<<"11";
for(int i=0;i<n;i++) cout<<"011";
cout<<endl;
return;
}
if(2*n+1==m){
cout<<"1";
for(int i=0;i<n;i++) cout<<"011";
cout<<endl;
return;
}
if(n==m+1){
cout<<"0";
for(int i=0;i<n-1;i++) cout<<"10";
cout<<endl;
return;
}
//平均分
int res=m-n;//一个0配一个1之后还剩几个1
for(int i=0;i<res;i++) cout<<"011";
for(int i=0;i<n-res;i++) cout<<"01";
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a
beautiful:任意相邻两个数,其中一个数是另一个数的因数,ai和bi的差的绝对值的和的两倍小于等于S
构造美丽数组(答案总是存在)–>万能通用的构造方法
与顺序无关,排个序
b1先等于a1,然后依次遍历,如果ai-1是bi-1的倍数,那么就可以,否则,bi就延续bi-1,但其实并不科学,不能证明,甚至自己觉得可以构造出反例
让数组b的奇数位都等于1,偶数位和数组a的偶数位对应相等或者让数组b的偶数位都等于1,奇数位和数组a的奇数位对应相等
这样的话,奇数位相差都为0或者偶数位相差都为0,其中有一种情况能满足差的绝对值的和的两倍小于等于S,只要分别构造出来,检验即可
trick:
1.两倍可以往奇偶位的方向想
2.相邻两个数满足一个数能被另一个数整除,比较特殊万能的就是1,因为1能够整除任意一个数
3.如果有一个思路,那么需要去验证正确性,如果代码比较容易写的话, 那么直接写代码验证,如果代码不容易写,那么就通过构造例子,看能否举出反例,这是策略
4.如果构造序列需要判断一些条件,比较麻烦,可以直接将可能的序列构造出来 ,检验即可
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=55;
int a[N],b[N];
int n;
void solve() {
cin>>n;
int sum=0;
for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
for(int i=1;i<=n;i++){
if(i%2==1) b[i]=1;
else b[i]=a[i];
}
int res=0;
for(int i=1;i<=n;i++){
res+=abs(a[i]-b[i]);
}
if(res*2<=sum){
for(int i=1;i<=n;i++) cout<<b[i]<<' ';
cout<<endl;
return;
}
for(int i=1;i<=n;i++){
if(i%2==1) b[i]=a[i];
else b[i]=1;
}
for(int i=1;i<=n;i++) cout<<b[i]<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
将正整数n表示成准二进制数之和
要求准二进制数的个数最少
准二进制数:只包含0和1的十进制数
因为最小可以为1(0用不着),所以所有数都是可得的
两位数的话,最大的准二进制数可以为11,最小为10
三位数的话,最小的准二进制数为100,最大的准二进制数为111
贪心
从大的开始贪
从高位到低位,该位为x,如果x小于等于1,那么就等于x,如果x大于1,那么后面所有位都变成1
这样贪是错的,因为这样可能使得1的个数过多
我们一位一位看,对于某一位来说,只有可能是0和1,然后我们就分别凑每一位就行了,比如说某一位是x,那么该位就需要x个1,最少个数即所有位上的数的最大值
那么如何构造呢?例如523,从最低位开始遍历,将3平均分到前3个数中,将20平均分到前2个数中,将500平均分到前5个数中
trick:
1.如果思考方向为贪心的话,尤其要验证思路,通过造极端复杂的样例,看思路是否正确
2.对于凑十进制数(只能存在1和0,主要是1),一个思考方向是拆位,分别凑每一位,方法是每一位上的数x平均放到前x个数中(要乘以幂)
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
int a[N];
int n;
void solve() {
cin>>n;
string s=to_string(n);
int len=s.size();
int cnt=0;
for(int i=0;i<len;i++) cnt=max(cnt,(int)(s[i]-'0'));
for(int i=len-1,mi=0;i>=0;mi++,i--){
int digit=s[i]-'0';
for(int j=0;j<digit;j++){
a[j]+=pow(10,mi);
}
}
cout<<cnt<<endl;
for(int i=0;i<cnt;i++) cout<<a[i]<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
一共有n个区间,第i个区间的单位长度权重为ci
操作:重新排列l,r,c
求所有区间权重之和最小是多少
可以得到所有区间长度和是一定的,然后n个区间的单位长度权重也是一定的,所以就是对一整段长度一定的区间进行分割,给予其不同的单位权重,使得总权重最小,那么就要让平均权重最小,将更小的单位权重给更长的区间,这样可以让平均权值最小
所以就是每次选取当前最短的区间,做法是先将l升序,将r放在set中,对于最大的l,在set中进行二分找到大于它的第一个,构成的区间即为最短的区间,然后在set中删除该元素
区间长度升序,然后权重降序,相乘并相加
trick:
1.找到什么是一定的,这个很重要,往往是解题的关键,比如区间总长度一定,要使得总权重最小,那么就要让平均权重最小2.在set中进行二分,s.upper_bound(x)表示set中大于x的第一个元素,返回其迭代器
3.set删除元素
erase(iterator) ,删除定位器iterator指向的值
erase(key_value),删除键值key_value的值
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int l[N];
int c[N];
int n;
void solve() {
cin>>n;
for(int i=0;i<n;i++) cin>>l[i];
set<int>s;
for(int i=0;i<n;i++) {
int x;
cin>>x;
s.insert(x);
}
for(int i=0;i<n;i++) cin>>c[i];
sort(l,l+n);
sort(c,c+n);
reverse(c,c+n);
vector<int>ans;
for(int i=n-1;i>=0;i--){
auto it=s.upper_bound(l[i]);//在set中找到大于l[i]的第一个元素,返回其迭代器
int r=*it;
ans.push_back(r-l[i]);
s.erase(it);
}
sort(ans.begin(),ans.end());
int res=0;
for(int i=0;i<n;i++){
res+=c[i]*ans[i];
}
cout<<res<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
P构造和为S的N个正整数,选择一个在[0,S]的一个元素k
V在以上数组中选择若干元素之和等于K才能赢
问P能否获胜
坑点:YES,NO
正整数,最小为1
手玩
N为1肯定YES
N小于等于S则YES,构造方法即为平均分,1是没有出现的
2 3
1 2
2 4
2 2
3 4
1 1 2
3 5
1 2 2
3 6
2 2 2
trick:
1.手玩找出的规律
2.平均分这一技巧是之前积累过的,为这题构造提供了一个好方法
#include
#define endl '\n'
#define int long long
using namespace std;
int n,s;
void solve() {
cin>>n>>s;
if(n==1){
if(s==1) cout<<"NO"<<endl;
else{
cout<<"YES"<<endl;
cout<<s<<endl;
cout<<s-1<<endl;
}
return;
}
if(2*n<=s){
cout<<"YES"<<endl;
int ave=s/n;//平均分
int res=s-ave*n;//剩下的
vector<int>ans;
for(int i=0;i<n;i++) ans.push_back(ave);
for(int i=0;i<res;i++) ans[i]++;
for(auto v:ans) cout<<v<<' ';
cout<<endl;
cout<<1<<endl;
}
else cout<<"NO"<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a,均为合数
选择小于等于11的一个整数m
1到m共m中颜色,给所有元素进行着色
要求:1到m都要被用到,所有元素都要有颜色,颜色相同的两个元素不能互质
题目一定有解
数据比较小,可以考虑暴力,直接对于每种颜色,去遍历其它数,如果不互质,那么就涂相同的颜色,这样不行,因为相同颜色的必须全部都不互质
如果n小于等于11的话,那么直接n种颜色全不同即可
可以简化一下,全部偶数都取同一种颜色,然后只要看奇数,通过手玩,由于均为合数,所以数比较少,然后由于合数可以分解成若干个质数的乘积,发现从3开始,5,7,11,13,17,19,23,29,31,一直到小于1000,用个质数筛就行了
坑点:读题还是看英文,当看不懂的时候再看一眼翻译,因为有些词翻译并不准确
trick:
1.合数可以分解成若干个质数的乘积(唯一),之前积累过的,在这里又用到了
2.分奇偶是一个很好的方向,如何去想呢?只要是有关数学的,比如因数,质数,合数
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
int a[N];
int color[N];
bool st[N];
int prime[N];
int n;
int cnt;
//欧拉筛
void get_prime(int n){
for(int i=2;i<=n;i++){
if(!st[i]) prime[cnt++]=i;
for(int j=0;prime[j]<=n/i;j++){
st[prime[j]*i]=true;
if(i%prime[j]==0) break;
}
}
}
void solve() {
cin>>n;
memset(color,0,sizeof color);
for(int i=1;i<=n;i++) cin>>a[i];
int ans=0;
map<int,int>mp;
for(int i=1;i<=n;i++){
for(int j=0;j<cnt;j++){
if(a[i]%prime[j]==0){
// cout<
if(mp[prime[j]]==0){
ans++;
color[i]=ans;
mp[prime[j]]=ans;
}
else color[i]=mp[prime[j]];
break;
}
}
}
cout<<ans<<endl;
for(int i=1;i<=n;i++){
cout<<color[i]<<' ';
}
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
get_prime(1000);
cin>>t;
while(t--) {
solve();
}
return 0;
}
初始共n个木板,长度为ai
共q个订单,+x代表收到长度为x的木板,-x代表取出长度为x的木板
问每次订单之后能否建造两个所需形状的仓库(两个正方形或者一个正方形一个长方形)
能否建造正方形取决于是否有四个长度一样的木板
能否建造长方形取决于在减去四个长度一样的木板后,是否还至少两组两个长度一样的木板
用set1存放有四个长度一样的木板,用set2存放有两个长度一样的木板
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,q;
void solve() {
cin>>n;
set<int>s1,s2;
map<int,int>mp;//桶计数
for(int i=1;i<=n;i++){
int x;
cin>>x;
mp[x]++;
if(mp[x]>=2) s2.insert(x);
if(mp[x]>=4) s1.insert(x);
}
cin>>q;
while(q--){
char ch;
int x;
cin>>ch>>x;
if(ch=='+'){
mp[x]++;
if(mp[x]>=2) s2.insert(x);
if(mp[x]>=4) s1.insert(x);
}
else{
mp[x]--;
if(mp[x]<2) s2.erase(x);
if(mp[x]<4) s1.erase(x);
}
if(s1.size()>=2) cout<<"Yes"<<endl;
else if(s1.size()==1){
int x=*s1.begin();
if(mp[x]>=8) cout<<"Yes"<<endl;
else if(mp[x]>=6){
if(s2.size()>=2) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
else{
if(s2.size()<=2) cout<<"No"<<endl;
else cout<<"Yes"<<endl;
}
}
else cout<<"No"<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
一共有n块积木,高度为hi(高度小于等于x)
将n块积木堆成m座塔,任意两座塔的高度差要小于等于x
坑点:YES,NO
如果不能建造,就输出NO,否则输出YES,并输出每个积木放在哪座塔中
可能平均分比较好
将n个积木分配到m组,使得任意两组差小于等于x
先升个序,然后按顺序循环放置,通过手玩,验证,发现任意两组是不会超的,应该是不会无解的
trick:
关于分配到几组的问题,就从第一组开始往后放就行了
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,m,x;
struct node{
int a,idx;
bool operator<(const node &W)const{
return a<W.a;
}
}q[N];
void solve() {
cin>>n>>m>>x;
for(int i=1;i<=n;i++) cin>>q[i].a,q[i].idx=i;
sort(q+1,q+1+n);
vector<int>ans(n+1);
int mm=1;
for(int i=1;i<=n;i++){
ans[q[i].idx]=mm;
mm++;
if(mm==m+1) mm=1;
}
cout<<"YES"<<endl;
for(int i=1;i<=n;i++) cout<<ans[i]<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[0,n])
构造数组b
while(a不为空),在a中选择前k个个数,将MEX放入b的末尾
使得数组b字典序最大
贪心,优先选择MEX最大的
trick:
作为mex题目的一个案例
坑点:
贪心地一段一段截取,最后不能忘了最后一段
1.O(1)地得到x是否存在于后缀中:从左到右扫描, 维护每一个数字当前出现的次数和总的次数,如果当前数字出现次数和该数字总个数相等,说明后缀中没有该数字了,在这里是看当前的mex是否在后缀中出现过,如果没出现,那么就不会出现比当前mex更大的了,那么就贪心的结束
2.如何求mex:利用set维护mex,一边遍历,一边将其放入set中
while(s.count(mex)) mex++;
不用担心这样会超时,因为它是一边遍历一边放入set,一边维护mex,mex并不是每次从0开始加的
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
cin>>n;
map<int,int>mp,mmp;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
set<int>s;
vector<int>ans;
int mex=0;
for(int i=1;i<=n;i++){
mmp[a[i]]++;
s.insert(a[i]);
while(s.count(mex)) mex++;
if(mmp[mex]==mp[mex]){
ans.push_back(mex);
mex=0;
s.clear();
}
}
if(s.size()) ans.push_back(mex);
cout<<ans.size()<<endl;
for(auto v:ans) cout<<v<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[0,2e5]),n大于等于2
ai表示社交能力,表示第i个人可以交谈几次
问一次会议最多可以交谈几次,并输出方案
贪心,优先消耗大的,如果先消耗小的,那么最大的只剩一个人了,没人和他交谈,那么次数都浪费掉了
坑点:
大于0的才能入队,因为入队是要去抵消的
trick:
1.往贪心考虑的思考方式可以是假设优先怎么样,如果不对那么就反过来,或者通过反面来衬托正面方法的优越性,想到了贪心的思路一定要造样例验证
2.优先消耗大的,想到优先队列大根堆,但是每次只能做一次操作,因为每做一次操作就会实时变化,最大的一直在变,千万不能一次操作就全部把最大和次大抵消了
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=2e5+10;
int n;
void solve() {
cin>>n;
priority_queue<PII>q;
for(int i=1;i<=n;i++){
int x;
cin>>x;
if(x) q.push({x,i});
}
vector<PII>ans;
while(q.size()>1){
auto t1=q.top();
q.pop();
auto t2=q.top();
q.pop();
ans.push_back({t1.second,t2.second});
t1.first--;
t2.first--;
if(t1.first>0) q.push(t1);
if(t2.first>0) q.push(t2);
}
cout<<ans.size()<<endl;
for(auto v:ans){
cout<<v.first<<' '<<v.second<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的序列,一共有k种颜色
给n个数着色
如果两个数相等,则不能涂一样的颜色
每种颜色用的次数必须相等(一共k种颜色,用的次数都要相等)
要求尽可能涂的数量最多
坑点:
题目读仔细,一开始不知道k种颜色都要用
trick:
本质上是一个分配问题,本题作为分配问题的一个案例
要将k组都分配,那么就从第1组开始到第k组,一组一组放
由于两个相同的数不能放在同一组,所以将所有数排个序,连续放在一起,这样只要不超过k个,就不会出现相同的数放在一起,具体做法是
map<int,int>mp; vector<PII>ans; for(int i=1;i<=n;i++){ mp[a[i]]++; if(mp[a[i]]<=k) ans.push_back({a[i],i}); } sort(ans.begin(),ans.end());
另外,排序后获取下标也不一定非得用结构体,也可以用以上方法,vector存放pair类型
包括下面也作为案例,共group组,每组k个,每组都通过加法取模来得到1到k
int group=ans.size()/k; for(int i=0;i<group*k;i++){ c[ans[i].second]=i%k+1; }
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=2e5+10;
int a[N];
int c[N];
int n,k;
void solve() {
cin>>n>>k;
memset(c,0,sizeof c);
for(int i=1;i<=n;i++) cin>>a[i];
map<int,int>mp;
vector<PII>ans;
for(int i=1;i<=n;i++){
mp[a[i]]++;
if(mp[a[i]]<=k) ans.push_back({a[i],i});
}
sort(ans.begin(),ans.end());
int group=ans.size()/k;
for(int i=0;i<group*k;i++){
c[ans[i].second]=i%k+1;
}
for(int i=1;i<=n;i++) cout<<c[i]<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[1,1e18]) n大于等于2
操作:选取两个数,将两数的差的绝对值放在数组的末尾
k次操作
求数组a中最小值最小是多少
坑点:
没有想到可以再次选择两个同样的数,这样的话,三次一定可以得到0
其它只要讨论k为1以及k为2的情况
细细分析,为什么当时没想到,因为当时一开始就奔着贪心的思路去了,想着对两个差值最小的进行操作,实际上错了,但是方向已经偏了,所以,如果想到了贪心,一定要先验证,手玩造复杂极端的样例,如果不对,那么就立马纠正思路
或者说是如果是脑子里一看到就立马蹦出的思路,一定要验证,因为往往会被经验主义所带偏了方向
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e3+10;
int a[N];
int n,k;
void solve() {
cin>>n>>k;
int minn=1e18;
for(int i=1;i<=n;i++) cin>>a[i],minn=min(minn,a[i]);
if(k>=3){
cout<<0<<endl;
return;
}
sort(a+1,a+1+n);
if(k==1){
for(int i=2;i<=n;i++){
minn=min(minn,a[i]-a[i-1]);
}
cout<<minn<<endl;
return;
}
vector<int>ans;
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
minn=min(minn,abs(a[i]-a[j]));
ans.push_back(abs(a[i]-a[j]));//操作一次得到的所有数,这是所有可能的情况,但是只能得到其中的一个
}
}
sort(ans.begin(),ans.end());
for(int i=1;i<=n;i++){
int pos=lower_bound(ans.begin(),ans.end(),a[i])-ans.begin();
if(pos<(int)ans.size()) minn=min(minn,abs(a[i]-ans[pos]));
if(pos>0) minn=min(minn,abs(a[i]-ans[pos-1]));
}
cout<<minn<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
当1在最左端的时候,产生的贡献为1,当1在最右端的时候,产生的贡献为10,当1在中间的时候产生的贡献均为11
trick:
如果没有思路,就手玩造不同情况的复杂的极端的样例
#include
#define endl '\n'
#define int long long
using namespace std;
int n,k;
string s;
void solve() {
cin>>n>>k;
cin>>s;
int l=-1,r=-1;
map<char,int>mp;
int cnt=0;
for(int i=0;i<n;i++){
if(s[i]=='1') cnt++;
}
for(int i=0;i<n;i++){
if(s[i]=='1'){
l=i;
break;
}
}
for(int i=n-1;i>=0;i--){
if(s[i]=='1'){
r=i;
break;
}
}
int res1=l,res2=n-1-r;
if(cnt==0){
cout<<0<<endl;
return;
}
if(cnt==1){
if(k>=res2) cout<<1<<endl;
else if(k>=res1) cout<<10<<endl;
else cout<<11<<endl;
return;
}
if(k>=res1+res2) cout<<11+11*(cnt-2)<<endl;
else if(k>=res2) cout<<1+11*(cnt-1)<<endl;
else if(k>=res1) cout<<10+11*(cnt-1)<<endl;
else cout<<11*cnt<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
n个灯
颜色已知,共R,G,B三种颜色
重新着色,使得相邻的灯颜色不同
问最少需要重新着色几个灯管
贪心,遍历,如果灯和前一个灯颜色相等,就要重新上色
trick:
一开始题目没读懂,题目一边读一边用自己的话记录,不存在读不懂的题目,问题在于读题时浮躁
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
string tmp="RGB";
string s;
void solve() {
cin>>n>>s;
int ans=0;
for(int i=1;i<n-1;i++){
if(s[i]==s[i-1]){
ans++;
for(int j=0;j<3;j++){
if(tmp[j]!=s[i-1]&&tmp[j]!=s[i+1]) s[i]=tmp[j];
}
}
}
if(s[n-1]==s[n-2]){
ans++;
for(int j=0;j<3;j++){
if(tmp[j]!=s[n-2]) s[n-1]=tmp[j];
}
}
cout<<ans<<endl;
cout<<s<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[1,1e8])n大于等于2
操作:选择一个整数x,将所有的ai替换为ai和x的差的绝对值,使得非降序
问有无这样的x,无解输出-1
如果有解,x在[0,1e9]
画一个数轴,从前往后遍历,如果前一个数小于后一个数,那么x必须选取在它们中间的左边
如果前一个数大于后一个数,那么x必须选取在它们中间的右边
一直取交集,如果交集为空,则无解
如果前一个数和后一个数相等,那么无论x取什么都可以,不能设区间
坑点:
如果前一个数和后一个数相等,那么无论x取什么都可以,不能设区间
trick:
1.没有思路就手玩,这题也是手玩做出来的
2.当出现差的绝对值时,可以画一个数轴,转化为点与点之间的距离,数形结合
3.非降序是整体的一个结果,我们聚焦到局部,如果相邻两两之间都非降序,那么整个序列自然就非降序了(整体->局部),但是注意必须始终保证朝一个方向遍历,只能修改后面的值,不能把前面的值修改
3.若要确定出一个数满足条件,我们可以以区间的思想,它可以在哪个范围内选择,在各种条件限制下,不断缩小区间,取交集
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int l=0,r=1e9;
for(int i=1;i<=n-1;i++){
if(a[i]==a[i+1]) continue;
if(a[i]<a[i+1]){
int rr=(a[i]+a[i+1])/2;
r=min(r,rr);
}
else{
int ll=(a[i]+a[i+1]+1)/2;
l=max(l,ll);
}
}
if(l<=r) cout<<l<<endl;
else cout<<-1<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[1,1e9])
beauty:数组a最长上升子序列的长度和最长下降子序列的长度取min
重新排列a,求最大的beauty
其中一个大,必然会导致另一个小,那么min也小
所以最好平均一下
与顺序无关,先排个序
然后如果每个数字都只有一个的话,那么肯定平均一下,答案为(数字种数+1)/2
但是如果每种数字不止一个,那么就可以把多余的数字往后放使得降序,这样的话,答案为max((数字的种数+1)/2,mp[digit]大于1的个数)
如果n为1的话,那么答案为1
大概思路是对的,但是应该分开来,cnt1为只有一个的数字,cnt2有多个数字的数字,答案为(cnt1+1)/2+cnt2
只有一个的数字应该取平均分,有多个的数字两边都可以分到
归结为两个集合,然后考虑哪些数字应该放在哪个集合中,以及顺序怎么放
trick:
作为一个案例
归结为两个集合,然后考虑哪些数字应该放在哪个集合中,以及顺序怎么放(这种题目,每一个数字只能选择其中一个集合放置)
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
cin>>n;
map<int,int>mp;
int maxn=0;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++,maxn=max(maxn,a[i]);
if(n==1){
cout<<1<<endl;
return;
}
int cnt1=0,cnt2=0;
for(auto v:mp){
if(v.second==1) cnt1++;
else cnt2++;
}
cout<<(cnt1+1)/2+cnt2<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
一共有n个顶点
树的n-1条边
给n-1条边赋值,使得树成为质数树
质数树:每条边都是质数,相邻两条边的和也是质数
如果有解,权值范围在[1,1e5]
如果无解,输出-1
想到用质数2,3
如果一个顶点有三个分支,肯定不行,因为质数除了2是偶数,其它都是奇数,两个奇数相加为偶数就不不是质数了,所以只能一个2,再一个奇数质数,再一个2,这样2+2=4也不是质数了,所以一个顶点最多两个分叉,即度数最大为2,所以只有一条单向路才有解,直接2,3,2,3交替赋值即可
记录两个顶点所连边的序号
从叶子节点开始dfs,2,3,2,3交替赋值给当前边的序号
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
vector<vector<int>>e(N);
int d[N];
int ans[N];
int n;
map<PII,int>mp;
void dfs(int u,int fa,int digit){
ans[mp[{u,fa}]]=digit;
for(auto v:e[u]){
if(fa==v) continue;
dfs(v,u,5-digit);
}
}
void solve() {
cin>>n;
memset(d,0,sizeof d);
memset(ans,0,sizeof ans);
mp.clear();
for(int i=1;i<=n;i++) e[i].clear();
for(int i=0;i<n-1;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
mp[{u,v}]=mp[{v,u}]=i+1;
d[u]++;
d[v]++;
}
for(int i=1;i<=n;i++){
if(d[i]>=3){
cout<<-1<<endl;
return;
}
}
int st;
mp[{st,0}]=0;
for(int i=1;i<=n;i++){
if(d[i]==1){
st=i;
break;
}
}
dfs(st,0,3);
for(int i=1;i<n;i++) cout<<ans[i]<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的字符串s和t,均由a,b,c组成
操作:将ab变成ba或者将bc变成cb
次数不限
问能否使s变成目标串t
a和c只能通过b进行移动,a和c彼此分割,所以将b去掉,字符串s和t应该是相等的
字符串s和t中字符a和c所处的对应的位置应该满足:s中的a不能在t中的a的前面,s中的c不能在t中的c的前面
trick:
给定串变成目标串问题
重点关注操作,手玩造不同情况的复杂的极端的样例,重点关注操作的对象,无非是ab和bc,将它们再分解成最基本的,无非是a,b,c三个基本单元,重点关注基本单元的变化
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<char,int>PII;
int n;
string s,t;
void solve() {
cin>>n;
cin>>s>>t;
string tmp1,tmp2;
vector<PII>ans1,ans2;
for(int i=0;i<n;i++){
if(s[i]!='b') tmp1+=s[i],ans1.push_back({s[i],i});
if(t[i]!='b') tmp2+=t[i],ans2.push_back({t[i],i});
}
if(tmp1!=tmp2){
cout<<"NO"<<endl;
return;
}
for(int i=0;i<(int)ans1.size();i++){
if(ans1[i].first=='a'){
if(ans2[i].second<ans1[i].second){
cout<<"NO"<<endl;
return;
}
}
else{
if(ans2[i].second>ans1[i].second){
cout<<"NO"<<endl;
return;
}
}
}
cout<<"YES"<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
加法恒等式:a+b=(a^b)+ 2 * (a&b)
由于a+b = 2 * (a^b),得a ^ b=2 * (a&b)= x
构造a和b
如果a^b为奇数,无解
从与以及异或本身的性质来看,如果a&b和a^b如果有任意一位同时为1,则 无解
a=(a&b)+(a^b)=x*(3/2)
b=a&b=x/2
trick:
1.加法恒等式:a+b=(a^b)+ 2 * (a&b)
2.如果a^b已知=x或者a&b已知=y,那么要确定a和b,只要进行分配1即可
比如说a&b中为1的位,a和b对应的该位均为1,然后其它位可一个为0,一个为1,也可以两个都为0,我们如果是构造的话,可以不用管0,所以构造a=b=a&b
再比如说ab中为1的位,a对应该位为1,b对应该位为0或者a对应该位为0,b对应该位为1,总之,一个为0,一个为1,我们如果是构造的话,只要满足一个为0,一个为1即可,那么我们可以把所有的1分给a,那么a为(a&b)或上(ab)
另外,(a&b)|(ab)=(a&b)+(ab),如果是或运算的话,只要没有位均为1,就可以直接加,无进位加法
#include
#define endl '\n'
#define int long long
using namespace std;
int x;
void solve() {
cin>>x;
if(x%2){
cout<<-1<<endl;
return;
}
if(x&(x/2)){
cout<<-1<<endl;
return;
}
cout<<(x|(x/2))<<' '<<x/2<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[0,1e9])
好:所有数的和等于所有数异或的两倍
最多添加三个不同的元素,使数组变好,一定有解
a+b+c=sum
abc=pre
a+b+c+pre=sum+pre
abcpre=prepre=0
a+b+c+pre+sum+pre=2*(sum+pre)
abcpre(sum+pre)=sum+pre
trick:
异或性质:
- x^0=x
- x^x=0
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n;
void solve() {
cin>>n;
int sum=0,pre=0;
for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i],pre^=a[i];
if(sum==2*pre){
cout<<0<<endl<<endl;
return;
}
cout<<2<<endl;
cout<<pre<<' '<<sum+pre<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[0,1e9])n大于等于2
良好序列:[a1,ai]全部与起来=[ai+1,an]全部与起来,对于i从1到n-1
对数组a重新排序,问几种序列是良好序列,mod 1e9+7
a[1]=a[2]a[3]…^a[n]
a[1]=a[1]a[1]=a[1]a[2]a[3]…a[n],
a[n]=a[1]a[2]a[3]^…a[n-1]
a[n]=a[n]a[n]=a[1]a[2]a[3]…a[n],
所以a[1]=a[n]=tmp=a[1]a[2]a[3]^…a[n]
由于a[1]和a[n]是所有数的与,所以中间可以随意放
trick:
1.对于位运算,更多的是一种数学式子上的东西,重在推导,所以把可得到的式子整整齐齐的一个一个列出来
2.关于位运算的题目,经常全部进行一次位运算
3.a & a = a
4.如果数组中的数全部都与起来等于tmp,那么tmp和任何一个数(数组当中的一个数)与,都等于tmp(根据第3点)
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10,mod=1e9+7;
int a[N];
int n;
void solve() {
cin>>n;
map<int,int>mp;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
int tmp=a[1];
for(int i=1;i<=n;i++) tmp&=a[i];
int ans=1;
ans=mp[tmp]*(mp[tmp]-1);
ans%=mod;
for(int i=n-2;i>=1;i--){
ans=ans*i%mod;
}
cout<<ans<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组(数[-5e8,5e8])n大于等于2
操作:将后缀所有元素加1或者减1
可以帮助他,可以修改一个整数(所有操作之前)
使得所有元素相等,问最少执行多少次操作
可以发现,一起变的数是同步增长或者同步减少的,对目标全部元素相等毫无帮助,所以一起进行操作的数应该是相等的,所以就从最后一个数开始,先让它变得和倒数第二个数相等,再让他们变得和倒数第三个数相等,一直到和第一个数相等
所以只要看相邻两个数的差,我们可以帮助他修改一个数,就看相邻两个数差最大的,变成0就行,不对,比如说
99 96 97 95,如果将96变成99,原本97到96需要1,96到99需要3,现在97到99需要2,所以应该看的是,将某个数改成相邻的数之后,相当于去掉这个数之后,差值减的最多的,所以只要分别枚举删除哪一个数
trick:
一边手玩一边关注被操作对象的变化,以它们之间的联系
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int sum=0;
int ans=1e18;
for(int i=n;i>=2;i--) sum+=abs(a[i]-a[i-1]);
ans=min(ans,sum-abs(a[1]-a[2]));
ans=min(ans,sum-abs(a[n]-a[n-1]));
for(int i=2;i<=n-1;i++){//枚举删除哪一个数
ans=min(ans,sum-(abs(a[i]-a[i-1])+abs(a[i]-a[i+1]))+abs(a[i-1]-a[i+1]));
}
cout<<ans<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
摩卡有节点1到n,戴安娜有节点1到n
初始边数不同,摩卡有m1条边,戴安娜有m2条边
问最多添加多少条边(两人的边必须同时添加,而且添加的边是一样的)
不在一个连通块就可以相连
数据比较小,直接暴力,O(n^2)
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=1010;
int p1[N],p2[N];
int n,m1,m2;
int find1(int x){
if(p1[x]!=x) p1[x]=find1(p1[x]);
return p1[x];
}
int find2(int x){
if(p2[x]!=x) p2[x]=find2(p2[x]);
return p2[x];
}
void solve() {
cin>>n>>m1>>m2;
for(int i=1;i<=n;i++) p1[i]=p2[i]=i;
while(m1--){
int u,v;
cin>>u>>v;
u=find1(u),v=find1(v);
p1[u]=v;
}
while(m2--){
int u,v;
cin>>u>>v;
u=find2(u),v=find2(v);
p2[u]=v;
}
vector<PII>ans;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) continue;
int a1=find1(i),b1=find1(j);
int a2=find2(i),b2=find2(j);
if(a1!=b1&&a2!=b2){
p1[a1]=b1;
p2[a2]=b2;
ans.push_back({i,j});
}
}
}
cout<<ans.size()<<endl;
for(auto v:ans) cout<<v.first<<' '<<v.second<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
共n个整数
n * n 的方阵,主对角线上放置1到n的排列
满足,1的连通块元素个数为1,2的连通块元素个数为2,以此类推
我们从上到下,数x,先往左看,如果放不了,就往下放,直到放置了刚好x个
trick:
如果样例中没有无解的情况,大概是没有无解的情况
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=510;
int p[N][N];
int n;
void solve() {
cin>>n;
memset(p,0,sizeof p);
for(int i=1;i<=n;i++) cin>>p[i][i];
for(int i=1;i<=n;i++){
int x=i,y=i;
int cnt=1;
while(1){
if(cnt==p[i][i]) break;
if(y-1>=1&&p[x][y-1]==0){
y--;
p[x][y]=p[i][i];
cnt++;
if(cnt==p[i][i]) break;
}
else if(x+1<=n&&p[x+1][y]==0){
x++;
p[x][y]=p[i][i];
cnt++;
if(cnt==p[i][i]) break;
}
if(cnt==p[i][i]) break;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cout<<p[i][j]<<' ';
}
cout<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组(数[-20,20])n小于等于20 数据比较小,考虑暴力
操作:将一个数加到另一个数上,可以自己加到自己
使非降序,最多50次操作就够了
如果都是负数,那么从后往前,后一个数加到前一个数上
如果都是正数,那么从前往后,前一个数加到后一个数上
如果有一个数是正数,那么就一直加自己加到大于20,然后每个数都加它,可以让每个数快速变成正数
trick:
如果操作对象有两个,考虑让一个定死,或者让两个都定死
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=25;
int a[N];
int n;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
vector<PII>ans;
int maxn=-25;
int maxi=0;
for(int i=1;i<=n;i++){
if(a[i]>maxn){
maxn=a[i];
maxi=i;
}
}
if(maxn<=0){
for(int i=n-1;i>=1;i--){
ans.push_back({i,i+1});
}
}
else{
while(maxn<=20){
ans.push_back({maxi,maxi});
maxn*=2;
}
a[maxi]=maxn;
for(int i=1;i<=n;i++){
if(a[i]<0){
ans.push_back({i,maxi});
a[i]+=a[maxi];
}
}
for(int i=1;i<=n-1;i++){
ans.push_back({i+1,i});
}
}
cout<<ans.size()<<endl;
for(auto v:ans) cout<<v.first<<' '<<v.second<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
n行m列的网格填入1到n * m
使得任意相邻两个数的差的绝对值不是质数,一定有解
行和列有一个为偶数,那么就可以1到n * m顺序填
如果m不是是质数,那么一个一个按顺序填
否则,先把所有偶数行输出,再把所有奇数行输出
trick:
1.注意一定要看数据范围,记下来,在这里n和m均大于等于4
2.构造题,往奇偶方向想,如果是矩阵构造题,往奇偶行方向想,在这里提供一种方法 ,先把所有偶数行输出,再把所有奇数行输出(相对于从1开始顺序填的矩阵)
3.矩阵构造题,一般是一边遍历一边输出,如果想把它们存在数组正确的位置上再全部输出比较困难
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
bool check(int x){
if(x<=1) return false;
for(int i=2;i<=x/i;i++){
if(x%i==0) return false;
}
return true;
}
void solve() {
cin>>n>>m;
if(!check(m)){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cout<<(i-1)*m+j<<' ';
}
cout<<endl;
}
}
else{
for(int i=2;i<=n;i+=2){
for(int j=1;j<=m;j++) cout<<(i-1)*m+j<<' ';
cout<<endl;
}
for(int i=1;i<=n;i+=2){
for(int j=1;j<=m;j++) cout<<(i-1)*m+j<<' ';
cout<<endl;
}
}
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a和b,数[1,2*n]
重新排列b,使得ai大于bi的个数刚好为x,无解输出No
贪心,a中x个最大的和b中x个最小的匹配,先保证有x个,并不是a中最大的和b中最小的,而是a中x个最大的升个序,b中x个最小的升个序,然后按顺序匹配,剩下的同理,为什么要升序后按顺序比,可以通过反向思考来感受这样做的合理性
trick:
1.贪心的思考方式:通过反向思考来感受贪心是否合理
2.两两匹配问题
分为三种
第一种是序列内部进行两两匹配,比如说最小和最大匹配,次小和次大匹配
第二种是两个不同的序列进行匹配,比如说每次匹配差值最大的,要么序列a的最大和序列b的最小匹配,要么序列a的最小和序列b的最大匹配,再比如说要求是ai大于bi刚好有x个,那么需要a中最大的x个去和b中最小的x个匹配,保证有x个,但不是a的最大和b的最小匹配,而是分别升序,然后按顺序匹配,合理性通过反向思考感受
第三种是两个一样的序列进行匹配,一般是通过自身元素的两两交换来满足某个要求,本质上就是自身和自身匹配
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=2e5+10;
int a[N];
int n,xx;
void solve() {
cin>>n>>xx;
vector<PII>ans;
for(int i=0;i<n;i++){
int x;
cin>>x;
ans.push_back({x,i});
}
multiset<int>s;
multiset<int,greater<int>>s1,s2;
for(int i=0;i<n;i++){
int x;
cin>>x;
s.insert(x);
}
sort(ans.begin(),ans.end());
reverse(ans.begin(),ans.end());
for(int i=0;i<xx;i++){
s1.insert(*s.begin());
s.erase(s.begin());
}
while(s.size()){
s2.insert(*s.begin());
s.erase(s.begin());
}
for(int i=0;i<xx;i++){
if(*s1.begin()>=ans[i].first){
cout<<"No"<<endl;
return;
}
a[ans[i].second]=*s1.begin();
s1.erase(s1.begin());
}
for(int i=xx;i<n;i++){
if(*s2.begin()<ans[i].first){
cout<<"No"<<endl;
return;
}
a[ans[i].second]=*s2.begin();
s2.erase(s2.begin());
}
cout<<"Yes"<<endl;
for(int i=0;i<n;i++) cout<<a[i]<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a(数[0,1e9])
操作:ai加上ai%10(可以多次对同一下标)
操作次数不限
问是否可以使所有元素相等
每次都只加个位,所以个位上的数字只要看个位即可
0
1->2->4->8->6->2
2->4->8->6->2
3->6->2->4->8->6->2
5->0
除了0和5特殊一些,其它均可以进入到2->4->8->6的循环中,并且从2到2会增加20,比如12->14->18->26->32
trick:
每次都只加个位,所以个位上的数字只要看个位即可
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
cin>>n;
map<int,int>mp;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]%10]++;
if(mp[0]||mp[5]){
for(int i=1;i<=n;i++){
if(a[i]%10!=0&&a[i]%10!=5){
cout<<"No"<<endl;
return;
}
}
for(int i=1;i<=n;i++){
if(a[i]%10==5) a[i]+=5;
}
for(int i=2;i<=n;i++){
if(a[i]!=a[i-1]){
cout<<"No"<<endl;
return;
}
}
cout<<"Yes"<<endl;
return;
}
for(int i=1;i<=n;i++){
while(a[i]%10!=2){
a[i]+=a[i]%10;
}
a[i]%=20;
}
for(int i=2;i<=n;i++){
if(a[i]!=a[i-1]){
cout<<"No"<<endl;
return;
}
}
cout<<"Yes"<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的字符串
分组,可以跳着挑,使得每组括号轴对称
问最少分几组
trick:
对于括号序列判断合法的问题,我们常用前缀和判断,遇到左括号 +1 ,遇到右括号 −1
一个括号(左边是左括号,右边得有右括号和左括号抵消)是合法的 ⟺ 对于所有的前缀 s u m i sum_i sumi 都满足 s u m i sum_i sumi≥0 ,且最后 s u m i sum_i sumi=0 。
得到折线图
而以下这一种是右括号在左边,右边有左括号和右括号抵消的合法括号序列的折线图
于是得到以下这种,当sum大于等于0时,是第一种的合法括号序列,当sum小于等于0时,是第二种的合法括号序列
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int ans[N];
int n;
string s;
void solve() {
cin>>n;
cin>>s;
int sum=0;
int flag=0;
for(int i=0;i<n;i++){
if(s[i]=='(') sum++;
else sum--;
if(sum>0) ans[i]=1,flag|=1;
else if(sum<0) ans[i]=2,flag|=2;
else if(sum==0) ans[i]=ans[i-1];
}
if(sum){
cout<<-1<<endl;
return;
}
if(flag!=3){
cout<<1<<endl;
for(int i=0;i<n;i++) cout<<1<<' ';
cout<<endl;
return;
}
cout<<2<<endl;
for(int i=0;i<n;i++) cout<<ans[i]<<' ';
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的01串a和b
操作:选择两个索引,01反转,如果两个索引相邻,那么代价为x,否则代价为y(x大于等于y)
操作不限次数
问a等于b的最小成本,无解输出-1
首先,需要反转的个数为奇数的话,那么无解
如果需要反转的个数为2的话,如果相邻的话,那么代价为min(x,2y),如果不相邻的话,那么代价为y
如果需要反转的个数大于2的话,那么代价为cnt/2y
#include
#define endl '\n'
#define int long long
using namespace std;
int n,x,y;
string a,b;
void solve() {
cin>>n>>x>>y;
cin>>a>>b;
vector<int>ans;
for(int i=0;i<n;i++){
if(a[i]!=b[i]) ans.push_back(i);
}
if(ans.size()%2){
cout<<-1<<endl;
return;
}
if(ans.size()==2){
if(ans[1]==ans[0]+1){
cout<<min(x,2*y)<<endl;
}
else cout<<y<<endl;
}
else{
cout<<ans.size()/2*y<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的只包含小写字母的字符串
操作:
二选一
1.将2到i反向加到s的前面
2.将i到n-1反向加到s的后面
将字符串变成回文串,最多操作30次,一定有解
有一种万能的方法:比如abcd,先将c移到最右边,变成abcdc,然后将bcd反向移到最左边,变成dcbabcdc,然后将第2个c移到最左边变成cdcbabcdc
trick:
重点关注操作对象,比如可以对连续一段进行操作,我们也可以考虑对单个进行操作
#include
#define endl '\n'
#define int long long
using namespace std;
string s;
void solve() {
cin>>s;
int len=s.size();
cout<<3<<endl;
cout<<'R'<<' '<<len-1<<endl;
len++;
cout<<'L'<<' '<<len-1<<endl;
cout<<'L'<<' '<<2<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
他有n个朋友,队列1到n
共m对互不相识的朋友
队列子段a到b如果都是好朋友,那么该子段就是好队列
问有几个子段是好队列
如果全部人都认识的话,那么第一个人作为区间右端点,有一个区间,第二个人作为区间右端点,有两个区间,…第i个人作为区间右端点,有i个区间
第i个人作为区间右端点,看它最多往左延申到哪里,看它左边包括它自己不相识的人的最大值取max
trick:
区间问题,往往可以将一个点作为右端点固定,然后看以它为右端点的区间
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,m;
void solve() {
cin>>n>>m;
memset(a,0,sizeof a);
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
if(x>y) swap(x,y);
a[y]=max(a[y],x);
}
int ans=0;
for(int i=1;i<=n;i++)
{
a[i]=max(a[i],a[i-1]);
ans+=i-a[i];
}
cout<<ans<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
trick:
1.vector
a(n,vector(m));可以直接cin>>a [i] [j] 2.如果要通过交换两个元素将一个序列排成非降序,可以另外放一个数组,先排好序,然后如果对应位置上的数不一样,那么这些位置需要交换
for(int i=0;i<n;i++){ sort(b[i].begin(),b[i].end()); }
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
cin>>n>>m;
vector<vector<int>>a(n,vector<int>(m));
vector<vector<int>>b(n,vector<int>(m));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>a[i][j];
b[i][j]=a[i][j];
}
}
for(int i=0;i<n;i++){
sort(b[i].begin(),b[i].end());
}
int l=-1,r=-1;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(a[i][j]!=b[i][j]){
l=j;
break;
}
}
for(int j=m-1;j>=0;j--){
if(a[i][j]!=b[i][j]){
r=j;
break;
}
}
if(l!=-1&&r!=-1) break;
}
if(l==-1&&r==-1){
cout<<1<<' '<<1<<endl;
return;
}
for(int i=0;i<n;i++){
swap(a[i][l],a[i][r]);
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(a[i][j]!=b[i][j]){
cout<<-1<<endl;
return;
}
}
}
cout<<l+1<<' '<<r+1<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
交互题
任何一个合数都可以分解为若干个质数的乘积(唯一)
trick:
这是做到的第二个交互题,当作案例
#include
#define endl '\n'
#define int long long
using namespace std;
int prime[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,4,9,25,49};
void solve() {
int cnt=0;
for(int i=0;i<19;i++){
cout<<prime[i]<<endl;
cout.flush();
string s;
cin>>s;
if(s[0]=='y') cnt++;
}
if(cnt<2) cout<<"prime"<<endl;
else cout<<"composite"<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
构造一个长度为n的字符串
使得它排成任何矩阵的形状,都没有相邻的字符相等
字符个数要求最少
如果n为奇数,那么直接两个字符ab,一直循环
如果n为偶数,那么找到第一个x,满足x不是n的因数,x-1是n的因数,x个字符一直循环
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin>>n;
if(n%2){
int flag=1;
for(int i=0;i<n;i++){
if(flag) cout<<'a';
else cout<<'b';
flag^=1;
}
}
else{
int num=1;
for(int i=3;;i++){
if(n%i&&n%(i-1)==0){
num=i;
break;
}
}
int cnt=0;
for(int i=0;i<n;i++){
cout<<(char)('a'+cnt);
cnt++;
if(cnt==num) cnt=0;
}
}
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组a
操作:
1.在数组a中选择一个m的倍数,将其变为m个ai/m
2.对于刚好连续m个相同的元素,用m*ai一个元素换掉m个元素
操作次数不限
问能否将a变成b
如果是m的倍数,那么就一直除以m,直到不是m的倍数,不能再分解,计算其最小单位的个数,以此类推
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=5e4+10;
int a[N],b[N];
int n,m;
int k;
void solve() {
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
a[n+1]=2e9;
cin>>k;
for(int i=1;i<=k;i++) cin>>b[i];
b[k+1]=2e9;
vector<int>ans1,ans2;
map<int,int>mp1,mp2;
int cnt=0;
int sum=1;
while(a[1]%m==0){
a[1]/=m;
sum*=m;
}
cnt+=sum;
for(int i=2;i<=n+1;i++){
sum=1;
while(a[i]%m==0){//分解为sum份a[i],a[i]已经分解成最小单位了
a[i]/=m;
sum*=m;
}
if(a[i]==a[i-1]) cnt+=sum;
else{
ans1.push_back(a[i-1]);
int len=ans1.size()-1;
mp1[len]=cnt;
cnt=sum;
}
}
cnt=0;
sum=1;
while(b[1]%m==0){
b[1]/=m;
sum*=m;
}
cnt+=sum;
for(int i=2;i<=k+1;i++){
sum=1;
while(b[i]%m==0){//分解为sum份a[i],a[i]已经分解成最小单位了
b[i]/=m;
sum*=m;
}
if(b[i]==b[i-1]) cnt+=sum;
else{
ans2.push_back(b[i-1]);
int len=ans2.size()-1;
mp2[len]=cnt;
cnt=sum;
}
}
// for(int i=0;i<(int)ans1.size();i++) cout<
// cout<
// for(int i=0;i<(int)ans2.size();i++) cout<
// cout<
// for(int i=0;i<(int)ans1.size();i++) cout<
// cout<
// for(int i=0;i<(int)ans2.size();i++) cout<
// cout<
if(ans1.size()!=ans2.size()){
cout<<"No"<<endl;
return;
}
for(int i=0;i<(int)ans1.size();i++){
if(ans1[i]!=ans2[i]||mp1[i]!=mp2[i]){
cout<<"No"<<endl;
return;
}
}
cout<<"Yes"<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的字符串
操作:对于所有长度为k的连续子串,从左到右依次颠倒
使得操作后的字典序最小,且选取的k尽量小
数据稍小,可以浅暴力,结果超时了,数据不算小,最多n^2
首先,需要将最小的字符放在第一个位置
所以先找到最小字符的位置
但是如果按照题目的步骤来的话必定会超时,然后通过找规律发现,最终结果是sk到sn,返回如果n和k奇偶性相同则加上sk-1sk-2…s1,否则加上s1s2…sk-1
trick:
如果按照题目的步骤很繁琐,必定会超时,那么可能可以手玩找规律直接一步得到经过那么多操作后的结果,做法是多手玩几个例子,得到最终的结果
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
cin>>n;
cin>>s;
char ch='z';
for(int i=0;i<n;i++){
if(ch>s[i]) ch=s[i];
}
vector<int>ans;
for(int i=0;i<n;i++){
if(s[i]==ch) ans.push_back(i);
}
// for(auto v:ans) cout<
// cout<
string tmp;
int k=1;
for(int i=0;i<n;i++) tmp+='z';
for(auto v:ans){//第v个位置为最小字符
int len=v+1;
string ss;
for(int i=v;i<n;i++) ss+=s[i];
string sss;
for(int i=0;i<v;i++) sss+=s[i];
if(n%2==len%2) reverse(sss.begin(),sss.end());
ss+=sss;
if(tmp>ss){
k=len;
tmp=ss;
}
}
cout<<tmp<<endl;
cout<<k<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
题解
1到n共n块转
ai为第i块转的重量
共三个集合,将n块砖分配到三个集合中,集合不能为空
分配完后,从三个集合中各取出一个砖块,得分为w1和w2的差的绝对值加上w2和w3的差的绝对值
然后设x为最少得分,问x最大为多少
集合分配问题,考虑将哪些数放在哪个集合中
考虑让两边和中间的差值大,中间放最大的或者最小的,但这样并不一定是最优的
trick:
比较巧妙,当作案例
1.想到贪心的思路,要先验证,构造极端复杂的样例,可以这样构造:差值超级大和差值超级小结合
2.贪心不可行的话,可提供的一种思路是一边遍历,一边将当前作为拐点,算出最优解,然后取所有中最优的,拐点结合集合分配问题,可以先将一种情况放好,然后进行移动,而且是连续一段进行移动
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
void solve() {
cin>>n;
set<int>s;
for(int i=1;i<=n;i++){
cin>>a[i];
s.insert(a[i]);
}
vector<int>ans;
while(s.size()){
ans.push_back(*s.begin());
s.erase(s.begin());
}
int len=ans.size();
if(len==1){
cout<<0<<endl;
return;
}
if(len==2){
cout<<2*(ans[1]-ans[0])<<endl;
return;
}
int res=ans[len-1]-ans[0]+ans[len-1]-ans[len-2];
for(int i=1;i<len;i++){
res=max(res,ans[i]-ans[0]+ans[i]-ans[i-1]);
}
res=max(res,ans[len-1]-ans[0]+ans[1]-ans[0]);
for(int i=0;i<len-1;i++){
res=max(res,ans[len-1]-ans[i]+ans[i+1]-ans[i]);
}
cout<<res<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
操作:选择相邻的两个数,让第一个数加上或减去它们差的绝对值
问最少几次操作,可以让所有元素相等,一定有解
操作1的意思其实是将小的数变成大的数,操作2的意思其实是将大的数变成小的数
看哪个数最多,那么就变得全部和它一样
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
typedef pair<int,int>PII;
int a[N];
int n;
void solve() {
cin>>n;
map<int,int>mp;
for(int i=1;i<=n;i++){
cin>>a[i];
mp[a[i]]++;
}
int num;
int cnt=0;
for(auto v:mp){
if(cnt<=v.second){
cnt=v.second;
num=v.first;
}
}
set<int>s;
for(int i=1;i<=n;i++){
if(a[i]==num) s.insert(i);
}
cout<<n-cnt<<endl;
int l=1;
int en=(*(--s.end()));
while(s.size()){
int pos=*s.begin()-1;
while(pos>=l){
if(a[pos]<num) cout<<1<<' '<<pos<<' '<<pos+1<<endl;
else cout<<2<<' '<<pos<<' '<<pos+1<<endl;
pos--;
}
l=*s.begin()+1;
s.erase(s.begin());
}
if(en<n){
for(int i=en+1;i<=n;i++){
if(a[i]<num) cout<<1<<' '<<i<<' '<<i-1<<endl;
else cout<<2<<' '<<i<<' '<<i-1<<endl;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
竖着的在第一列抵消,横着的在第三行抵消
#include
#define endl '\n'
#define int long long
using namespace std;
string s;
void solve() {
cin>>s;
int n=s.size();
bool ok1=false,ok2=false;//ok1表示竖着的有没有放在(1,1),ok2表示横着的有没有放在(3,3)
for(int i=0;i<n;i++){
if(s[i]=='0'){
if(!ok1) cout<<1<<' '<<1<<endl,ok1=true;
else cout<<3<<' '<<1<<endl,ok1=false;
}
else{
if(!ok2) cout<<3<<' '<<3<<endl,ok2=true;
else cout<<3<<' '<<1<<endl,ok2=false;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
关于对称轴翻转,只要看左边一半就行,记录翻转的次数
利用差分和前缀和
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int b[N];
int sum[N];
string s;
int m;
void solve() {
cin>>s;
int n=s.size();
s=' '+s;
cin>>m;
for(int i=0;i<m;i++){
int x;
cin>>x;
a[i]=x;
if(x>n/2) x=n-x+1;
b[x]++;
}
for(int i=1;i<=n/2;i++) sum[i]=sum[i-1]+b[i];
for(int i=1;i<=n/2;i++){
if(sum[i]%2==1) swap(s[i],s[n-i+1]);
}
for(int i=1;i<=n;i++) cout<<s[i];
cout<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
贪心
乘i看作加i次
考虑从前往后,什么时候断开,当后缀和大于0那么就断开,如果后缀和小于0断开是不优的,因为放在后面,让负的多加了
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int last[N];
int n;
void solve() {
cin>>n;
memset(last,0,sizeof last);
for(int i=1;i<=n;i++) cin>>a[i];
last[n]=a[n];
for(int i=n-1;i>=1;i--) last[i]=a[i]+last[i+1];
vector<int>ans;
int sum=a[1];
for(int i=2;i<=n;i++){
if(last[i]>0){
ans.push_back(sum);
sum=a[i];
}
else sum+=a[i];
}
if(sum) ans.push_back(sum);
int res=0;
for(int i=0;i<(int)ans.size();i++){
res+=(i+1)*ans[i];
}
cout<<res<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
构造大小最小的多样性矩阵,无解输出0
1行1列无解
特判行为1和列为1的情况
发现一种构造方式:第一行放r+c的后c个数,然后第i行是第1行的i倍
#include
#define endl '\n'
#define int long long
using namespace std;
int r,c;
void solve() {
cin>>r>>c;
if(r==1&&c==1){
cout<<0<<endl;
return;
}
if(r==1){
for(int i=2;i<=c+1;i++) cout<<i<<' ';
cout<<endl;
return;
}
if(c==1){
for(int i=2;i<=r+1;i++) cout<<i<<endl;
return;
}
int m=r+c;
int st=m-c+1;
for(int i=1;i<=r;i++){
for(int j=st;j<=m;j++){
cout<<i*j<<' ';
}
cout<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的01串a和b
一次操作:
选择l,r
1.串a[l,r]01反转
2.串b[1,l-1]01反转或者[r+1,n]01反转,如果存在就必须01反转
使得两个串全部都变成0,最多操作n+5次,无解输出NO
感觉这个操作限制太大,某种序列才能成功全部变成0
串b,要么和a相等,要么和a相反才可以
每次操作后,串a和串b要么完全相同,要么完全相反
那么就把串a先全部变成0,如果串b已经全部为0了,那么结束,如果串b全为1,那么(1,1),(2,n),(1,n)
trick:
如果整个序列并不是一段一段连续的,而是交替的,那么对一段进行操作不好操作,考虑对单个进行操作
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int>PII;
int n;
string a, b;
void solve() {
cin >> n;
cin >> a >> b;
string c = a;
for (int i = 0; i < n; i++) {
if (c[i] == '1') c[i] = '0';
else c[i] = '1';
}
if (b != a && b != c) {
cout << "NO" << endl;
return;
}
bool ok=false;
if(a[0]=='1') ok=true;
vector<PII>ans;
for(int i=0;i<n;i++){
if(a[i]=='1') ans.push_back({i+1,i+1});
}
int cnt=ans.size();
if(ok) cnt--;
if(cnt%2){
if(b[0]=='0') b[0]='1';
else b[0]='0';
}
if(b[0]=='1'){
ans.push_back({1,1});
ans.push_back({2,n});
ans.push_back({1,n});
}
cout<<"YES"<<endl;
cout<<ans.size()<<endl;
for(auto v:ans) cout<<v.first<<' '<<v.second<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}
n * n的01矩阵
bij为第i行第j列
构造A1,A2,…An,满足如果Ai是Aj的适当子集,那么bij=1,否则bij=0
一定有解
A2->A1->A4
A3->A4
感觉有点像拓扑,但具体感觉又不会
可以拓扑,首先让点i初始都只有i,然后拓扑排序,子集中有的,全都给它的超集
trick:
很妙,让点i初始都只有i,当作案例
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
char b[N][N];
int d[N];
int n;
set<int>s[N];
void solve() {
cin>>n;
memset(d,0,sizeof d);
vector<vector<int>>e(n+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>b[i][j];
if(b[i][j]=='1'){
e[i].push_back(j);
d[j]++;
}
}
}
for(int i=1;i<=n;i++) s[i].clear();
for(int i=1;i<=n;i++) s[i].insert(i);
queue<int>q;
for(int i=1;i<=n;i++){
if(!d[i]) q.push(i);
}
while(q.size()){
int t=q.front();
q.pop();
for(auto v:e[t]){//t为子集,v为超集
for(auto x:s[t]) s[v].insert(x);
d[v]--;
if(!d[v]) q.push(v);
}
}
for(int i=1;i<=n;i++){
cout<<s[i].size()<<' ';
for(auto v:s[i]) cout<<v<<' ';
cout<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
第一次我们选择前k段,之后每一次都要先选前面的若干个0,这样一次就只能选k−1段。
trick:
将一个数分解x为若干个数的和,构造题的话随我们自己构造,我们可以分解为一个x,然后其它全补0,此时想补几个0就补几个0
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
int a[N];
int n,k;
void solve() {
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
int cnt=1;//一共有cnt种数字
for(int i=2;i<=n;i++){
if(a[i]!=a[i-1]) cnt++;
}
if(k==1&&cnt!=1) cout<<-1<<endl;
else if(k==1) cout<<1<<endl;
else{
int ans=0;
if(cnt>=k) cnt-=k,ans++;
ans+=cnt/(k-1)+(cnt%(k-1)!=0);
cout<<ans<<endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}