n个人,分成m组,每个组至少分一个人
靠一些直觉,分别对最大和最小进行思考
最大:除一组外其它全为1
最小:平均分,那么怎样平均分呢?
trick:平均分的做法:先每组分配n/m,如果n%m不为0的话,那么取n%m个加1
主要是对平均分这一技巧的掌握
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
cin>>n>>m;
int t=n-m+1;
int maxn=t*(t-1)/2;
int x=n/m;
int y=n%m;
int minn=(x+1)*x/2*y+x*(x-1)/2*(m-y);
cout<<minn<<' '<<maxn<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
9 * 9的数独,最多改掉9个数,使得每行,每列,每个3 * 3都至少有两个相同的元素(题目说一定有解)
直觉是修改的数值每行每列都不一样,比如(1,1),(2,2),…(9,9)通过样例来验证,是适用的
那么具体改成什么值呢?由于原本这是一个数独,即每行1到9不重复,没列1到9不重复,3 * 3中1到9不重复,所以只要改成不一样的就会造成它所在行,列,3 * 3会出现至少两个相同的元素,发现这样不行,还得再改改,这样的话很多3 * 3还是没有改掉,所以还要考虑3 * 3
trick:直接对以下进行修改
(1,1) (2,4)(3,7)
(4,2) (5,5)(6,8)
(7,3) (8,6)(9,9)当然,这里也有一定的技巧,就是横纵坐标分别放到dx数组和dy数组里,直接遍历即可,而不是写9个if
#include
#define endl '\n'
//#define int long long
using namespace std;
char a[10][10];
int dx[9]={1,2,3,4,5,6,7,8,9};
int dy[9]={1,4,7,2,5,8,3,6,9};
void solve() {
for(int i=1;i<=9;i++){
for(int j=1;j<=9;j++){
cin>>a[i][j];
}
}
for(int i=0;i<9;i++){
int x=dx[i],y=dy[i];
if(a[x][y]=='9') a[x][y]='1';
else a[x][y]=(char)(a[x][y]+1);
}
for(int i=1;i<=9;i++){
for(int j=1;j<=9;j++){
cout<<a[i][j];
}
cout<<endl;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
n个整数
ai表示值,bi表示类型
操作:选择两个类型不同的数,交换它们
操作不限次数,使得非降序,问是否可以
只要分别将两种类型的升序,然后合并在一起,再检验是否非降序
难点在于怎么实现:其实很简单,就是先把两种类型的数分别放在不同的multiset里,然后依次按照类型赋值回去即可
由于multiset中的erase是删掉的是与之相同的所有元素,我要删第一个,不知道怎么删,所以改成小根堆
样例一直错,然后才发现题目读错了,全都白分析了,不对,题目读对了,但是在分析的时候错乱了,刚好把题意记反了,记成交换相同类型的了
这告诉我们一个道理:先别急着敲代码,先看看想出的方法是不是对样例适用,应该至少保证样例全都适用
后面想的是进行一个类似于冒泡排序,就是如果类型不同且大小逆序就互换,但是时间复杂度肯定不允许
trick:操作是对某个数进行交换且操作次数不限,那么看两两交换是否可以扩展到更多数的交换,这里只要有0和1的交错,那么就全部都可以随意交换
#include
#define endl '\n'
//#define int long long
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int n;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
bool flag=true;
int cnt_0=0,cnt_1=0;
if(b[1]==0) cnt_0++;
else cnt_1++;
for(int i=2;i<=n;i++){
if(a[i-1]>a[i]) flag=false;
if(b[i]==0) cnt_0++;
else cnt_1++;
}
if(flag){
cout<<"Yes"<<endl;
return;
}
if(cnt_0==0||cnt_1==0) cout<<"No"<<endl;
else cout<<"Yes"<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
长度为n的数组
ai为正整数
操作:选择两个数,如果它们的最大公因数等于整个数组最小的那个就可以交换
操作次数不限
要求使a非降序
想想是否可以扩展一下交换
数组中最小的数在数组给定之后就已经确定下来了
至此都没什么解题的突破口
trick:
这类题都是有一般套路的
这类题的特征:操作是满足某性质的两个数可以交换,操作次数不限,目标是排好序
思考方向为是否有中间量可以作为交换的媒介,两数通过中间的媒介进行交换,某个数如果和中间量有联系的话,那么就可以和其它任意和中间量有联系的进行交换,从而达到自由移动
另外一种思考的方向是,我们要的结果是确定的,所以先排个序,结果确定下来,然后看位置不正确的数是否全部都可以自由移动
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int n;
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
void solve() {
cin>>n;
int minn=2e9;
for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i],minn=min(minn,a[i]);
sort(b+1,b+1+n);
for(int i=1;i<=n;i++){
if(a[i]!=b[i]){
if(gcd(a[i],minn)!=minn){
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;
}
1到n张桌子
长度为n的01字符串,1代表餐桌有人,0代表没人
保证给定字符串中任意两个1相差距离大于k
然后问最多将几个0变成1使得任意两个1相差距离仍然大于k
如果当前字符为0的话,查找往后k个距离内是否有1,如果没有就可以将该位置的0变为1,并跳到i+k的位置
如果当前字符为1的话,那么直接跳到i+k的位置
trick:
当出现最的时候,一种思路就是贪心:最好是怎么样,实在没办法才舍弃
贪心其中一种方向就是从前往后遍历,一边遍历一边贪,若可以将该位置的0变成1则变,尽可能多
#include
#define endl '\n'
#define int long long
using namespace std;
const int N = 2e5 + 10;
int a[N];
int n, k;
string s;
void solve() {
cin >> n >> k;
cin >> s;
vector<int>pos;//存放1的位置
for (int i = 0; i < n; i++) {
if (s[i] == '1') pos.push_back(i);
}
int ans = 0;
for (int i = 0; i < (int)s.size(); i++) {
if (s[i] == '0') {
int pos1 = upper_bound(pos.begin(), pos.end(), i) - pos.begin();
if (pos1 >= 0 && pos1 < (int)pos.size()) {
if(pos[pos1] - i > k){
ans++;
i = i + k;
}
}
else{
ans++;
i=i+k;
}
} else {
i = i + k;
}
}
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;
}
矩阵
共m列,2行
aij表示硬币数量
博弈
起点为(1,1),终点为(2,m)
操作:往右移动一格或者往下移动一格
最终分数为Bob获取的硬币数量
Alice从起点走到终点,拿取硬币
Alice想使得分数最小
Bob再走,想使分数最大
两者独立,互不相关
有关于数学数字,归结为数学敏感,手玩,找规律
trick:
1.有关于数学数字,归结为数学敏感,手玩,找规律
2.不要乱加特判,情况虽然简单,但不要也开始就加上,先看一般情况能否覆盖特殊情况,如果可以覆盖,那么它就不是特殊情况,加了特判反而容易错
这边对m=2时特判反而错了,属于画蛇添足了
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[3][N];
int last[N],pre[N];
int m;
void solve() {
cin>>m;
for(int i=1;i<=2;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
//特判
if(m==1){
cout<<0<<endl;
return;
}
//预处理第一行的后缀和
last[m]=a[1][m];
for(int i=m-1;i>=1;i--) last[i]=last[i+1]+a[1][i];
//预处理第二行的前缀和
pre[1]=a[2][1];
for(int i=2;i<=m;i++) pre[i]=pre[i-1]+a[2][i];
//遍历
int ans=2e9;
ans=min(ans,pre[m-1]);
ans=min(ans,last[2]);
for(int i=1;i<=m-2;i++){//i为第二行前缀的长度
int len=m-i-1;//第一行后缀的长度
ans=min(ans,max(pre[i],last[m+1-len]));
}
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,均为正整数
good数组:所有相邻两个数最大公因数均为1
操作:选择任意两个数,将其中一个数变成小的那个数min,另一个数随便变成一个大于等于min的数
最多操作n次,可以证明一定可以实现,变成good数组
输出操作
有一个很重要的性质:相差为1的两个数的最大公因数肯定为1,这在之前做题的时候做到过
把相邻的两个数变成相差为1的两个数,n次绝对够了,而且是刚好n-1次,想错了,没有想到修改后面就把前面的数给改了
为了避免修改前面的数会把后面的数给改掉,需要按升序的顺序来,这样ok了,这样还是不对,因为虽然排序了,但是原本的顺序是没有打乱的
而升序还有一个原因----该题与顺序无关,先排个序
这题最小的数可以保留,然后其它都可以变成大于等于最小数的任何数,那么可以奇数位全放最小数,偶数位全放最小数+1,或者偶数位全放最小数,奇数位全放最小数+1
trick:
1.相差为1的两个数的最大公因数肯定为1,这是之前做题做到过的,一个很重要很关键的性质,但是这里还要再拓展一下,题目要求是任意相邻两个数的最大公因数均为1,可能会想到每次都加1,呈现一个升序的状态,但是也可以减1,加1,这样就分为奇数位和偶数位,奇数位均相同,偶数位均相同,奇数位上的数和偶数位上的数差1
2.将一个数和另一个数联系起来,输出的时候输出这两个数,包括上次做到的输出生成树一条边的两个端点,看是否可以其中一个数就固定下来
3。挖掘操作(动作)的性质,想想哪些是一定的,首先做什么是一定的,在这里是最小的那个数一定是可以保留的
#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;
for(int i=1;i<=n;i++) cin>>a[i];
int pos=1,minn=a[1];
for(int i=2;i<=n;i++){
if(a[i]<minn){
minn=a[i];
pos=i;
}
}
cout<<n-1<<endl;
if(pos%2==1){
for(int i=1;i<=n;i++){
if(pos==i) continue;
if(i%2==0) cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]+1<<endl;
else cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]<<endl;
}
}
else{
for(int i=1;i<=n;i++){
if(pos==i) continue;
if(i%2==0) cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]<<endl;
else cout<<pos<<' '<<i<<' '<<a[pos]<<' '<<a[pos]+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和数组b,01串
操作:取长度为len的前缀,将0变成1,将1变成0,然后翻转
将字符串a变成b,需操作几次(最多3*n次,一定有解)
3次操作:翻转前i个字符,翻转第1个字符,再翻转前i个字符,可以将第i个字符进行翻转–这个规律真的很难发现,通过手玩也很难,可以记一下这个规律
trick:
1.一定有解,最多3*n次–>肯定可以构造出一种万能的通用的解,因为最多n个字符,然后通过3可以猜测每3次就可以将一个字符翻转
2.非常通用的一种方法–手玩
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
string a,b;
void solve() {
cin>>n;
cin>>a>>b;
vector<int>ans;
for(int i=0;i<n;i++){
if(a[i]!=b[i]){
ans.push_back(i+1);
ans.push_back(1);
ans.push_back(i+1);
}
}
cout<<ans.size()<<' ';
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;
}
构造序列,每个数大于1,所有数乘积为n,后一个数是前一个数的倍数,长度要最大(一定有解)
分解质因数,将出现次数最多的质因数(假设k个)输出k-1个,剩下一个和其它乘积
trick:
数学知识:每个合数都可以写成几个质数相乘的形式(唯一),其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数,这样得到的分解因数的序列一定是最长的,因为质数不能再分解了,然后将质因数乘积合成一个因数,这样可以有办法满足题意得最长序列
分解质因数的模板:试除法
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin>>n;
int m=n;
vector<int>ans;
map<int,int>mp;
for(int i=2;i<=n/i;i++){
if(n%i==0){
ans.push_back(i);
while(n%i==0) {
n/=i;
mp[i]++;
}
}
}
if(n>1){
ans.push_back(n);
mp[n]++;
}
// for(int i=0;i<(int)ans.size();i++) cout<
int maxn=mp[ans[0]];
int maxi=ans[0];
for(int i=1;i<(int)ans.size();i++){
if(maxn<mp[ans[i]]){
maxn=mp[ans[i]];
maxi=ans[i];
}
}
cout<<maxn<<endl;
for(int i=0;i<maxn-1;i++) cout<<maxi<<' ';
cout<<m/(int)pow(maxi,maxn-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个神奇宝贝
ai表示神奇宝贝的实力(均不同)
按顺序挑选神奇宝贝,加减交替,得到该支军队的实力,求出最大兵力
开头和结尾肯定是要加的(让个数为奇数,不然后面减一个肯定变小)
通过手玩造样例发现,比如8 7 6 5 4,如果选择8,7,6,5,10,那么最终结果是8-7+6-5+10=12,而选择8,5,10,则为8-5+10=13,所以一加一减选择一个最大的和最小的,它其实就是一个波峰波谷,每次都选择波峰和波谷即可
trick:
1.手玩–造样例2.找波峰和波谷
波峰:a[i] > a[i - 1] && a[i] > a[i + 1]
波谷:a[i] < a[i - 1] && a[i] < a[i + 1]
#include
#define endl '\n'
#define int long long
using namespace std;
const int N = 3e5 + 10;
int a[N];
int n, q;
void solve() {
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
//找波峰和波谷
bool flag = 1; //1表示要找波峰,0表示要找波谷
vector<int>ans;
if (a[1] > a[2]) ans.push_back(a[1]), flag = 0;
for (int i = 2; i <= n - 1; i++) {
if (flag == 1) { //找波峰
if (a[i] > a[i - 1] && a[i] > a[i + 1]) ans.push_back(a[i]), flag = 0;
} else if (flag == 0) { //找波谷
if (a[i] < a[i - 1] && a[i] < a[i + 1]) ans.push_back(a[i]), flag = 1;
}
}
if (a[n] > a[n - 1]) ans.push_back(a[n]);
int res = 0;
for (int i = 0; i < (int)ans.size() - 1; i += 2) {
res += ans[i] - ans[i + 1];
}
res += ans[ans.size() - 1];
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;
}
长度为n的数组a,数的范围为[0,1e9]
共m行询问,每次给定两个整数x和y,计算从城市x到城市y需要花费最少的硬币数
共m座城市,位于ai,按升序排列
对于一座城市,离它最近的城市是唯一的
操作:从城市x到y,花费它们距离的金币或者到离x最近的城市(唯一的)花费1金币
x和y在两个方向,x朝着y方向走,一直走最近,直到最近的是往回走,千万不能走回头路,此时直接花费距离金币到y
并查集不可行,并查集不能有环,那就用两个并查集,往右的和往左的,但是这样是错的,因为并不是走到并查集的头就断了,可以先利用城市距离过去再继续走最近城市
由于不能走回头路,所以我们利用一个前缀和以及后缀和就可以了,如果可以走最近城市那么就加1,否则就加两城市之间的距离
不能走回头路,然后如果能走最近城市就走最近城市,如果不能走就花费距离金币,这样花费代价是最小的
trick:
1.多次询问区间以及不能走回头路,考虑方向为前缀和
2.并查集是不能出现环的
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int pre[N],last[N];
int n,m;
void solve() {
cin>>n;
memset(pre,0,sizeof pre);
memset(last,0,sizeof last);
for(int i=1;i<=n;i++) cin>>a[i];
//前缀和
for(int i=1;i<n;i++){
if(i==1) pre[i+1]=pre[i]+1;
else{
if(abs(a[i]-a[i+1])<abs(a[i]-a[i-1])) pre[i+1]=pre[i]+1;
else pre[i+1]=pre[i]+abs(a[i]-a[i+1]);
}
}
//后缀和
for(int i=n;i>1;i--){
if(i==n) last[i-1]=last[i]+1;
else{
if(abs(a[i]-a[i-1])<abs(a[i]-a[i+1])) last[i-1]=last[i]+1;
else last[i-1]=last[i]+abs(a[i]-a[i-1]);
}
}
cin>>m;
while(m--){
int x,y;
cin>>x>>y;
if(x<y) cout<<pre[y]-pre[x]<<endl;
else cout<<last[y]-last[x]<<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对城市不能建设道路
一定有解
输出最少建设多少条道路,输出具体建设情况
记录不能建设道路的情况,记录每个城市不能用到的次数,次数最少的x去匹配那些可以和它连的,对于不能和x连的,看是否可以连到x的一级分支上
数据比较小,可以选择暴力
trick:
1.当两者需要联系起来时输出,往往其中一个可以定死,这是之前总结过的,在这里是解题的关键
2.数据比较小,直接暴力
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1010;
int n,m;
bool st[N][N];
void solve() {
cin>>n>>m;
map<int,int>mp;
memset(st,false,sizeof st);
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
st[x][y]=st[y][x]=true;
mp[x]++;
mp[y]++;
}
int minn=2e9,mini=-1;
for(int i=1;i<=n;i++){
if(minn>mp[i]){
minn=mp[i];
mini=i;
}
}
cout<<n-1<<endl;
vector<int>ans;
vector<int>res;
for(int i=1;i<=n;i++){
if(i==mini) continue;
if(!st[mini][i]) cout<<mini<<' '<<i<<endl,res.push_back(i);
else ans.push_back(i);
}
for(int i=0;i<(int)ans.size();i++){//枚举和mini不能相连的
for(int j=0;j<(int)res.size();j++){
if(!st[ans[i]][ans[j]]) cout<<ans[i]<<' '<<ans[j]<<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])
操作:选择一个索引i,保证ai不是i+1的倍数,删掉ai
直到序列为空、
问是否可以删除整个序列
手玩
要想删掉第一个数,那么第一个数就不能是2的倍数(第一个数要看的索引是固定的,是2)
如果全部是奇数,那么就一直删第一个数
删掉后面不会影响前面,删掉前面会影响后面
先把能删的都删掉,然后剩下的就都是不能删的了
然后只要把第一个删掉,所有都错一位,这样全部都变成能删的了,然后从后往前一个一个删
综上所述,如果第一个数是奇数的话, 那么Yes,否则N
全错了,这样错位下来不一定,比如说30,既是5的倍数,又是6的倍数
该题的做法是假设第i个数前面的数都可以被删掉,那么第i个数只要在2到i+1中有一个不是因数,也可以被删掉,如果有一个数,2到i+1全部都是它的因数的话,那么它不可能被删掉
由于2乘以3,一直乘到大概23就超过1e9了,所以最多判断22个数,这样双重循环是不会超时的
trick:
1.整除想到因子
2.对于删数问题,肯定是一位一位删,不可能一下子全部都删掉,其中一个思考的方向是假设前面的数全部都可以删掉,然后如何删掉该数
3.如果x是很多数的倍数,那么x也是它们最小公倍数的倍数,判断x是否是很多数的倍数,只需看是否是它们最小公倍数的倍数,一个一个判断是否是因子,那么阶乘级别的,判断次数不会很多,就会有一个不是因子
#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;
for(int i=1;i<=n;i++) cin>>a[i];
bool flag=true;
for(int i=1;i<=n;i++){
bool ok=false;
for(int j=i+1;j>=2;j--){
if(a[i]%j!=0){
ok=true;
break;
}
}
flag&=ok;
}
if(flag) 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
然后让1到2*n两两匹配,使得它们的和按顺序分别是加1
它们所有的和是固定的,然后每个数都差1,求和就是等差数列,等差数列一共n个数
可以列出一个式子,假设第一个和为x,那么(x+x+n-1) * n /2=(1+2 * n)* 2 * n /2
得到x=(3 * n+3)/2
然后n个和即为,x,x+1,…x+n-1
通过手玩发现构造规律:将1到2n分成两半,左半边的x-(n+1)到n分别按次序和右半边相加,然后右半边剩下的按次序和左半半剩下的相加即可
trick:
突破口在于和是固定的,一旦找到某个是不变的,它就是题目的突破口
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin>>n;
if((3*n+3)%2){
cout<<"No"<<endl;
return;
}
int x=(3*n+3)/2;//第一个和
int pos=x-(n+1);
cout<<"Yes"<<endl;
int idx;
for(int i=n+1,j=pos;j<=n;i++,j++){
cout<<i<<' '<<j<<endl;
idx=i;
}
for(int i=idx+1,j=1;i<=2*n;i++,j++){
cout<<i<<' '<<j<<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的数组x(数的范围[0,1e9])
增长序列:任意i,ai & ai+1 =ai(n为1也是增长序列)
构造序列b,ai^bi=ci,使得c是增长序列(这里ai并不是增长序列)
求b的字典序最小
b 1 b_1 b1可以任意,直接取为最小,为0
即( a i a_i ai$b_i$)&($a_{i+1}$ b i + 1 b_{i+1} bi+1)= a i + 1 a_{i+1} ai+1^ b i + 1 b_{i+1} bi+1
得到 a i a_i ai$b_i$中1所在的位,$a_{i+1}$ b i + 1 b_{i+1} bi+1也都是1,由于 a i + 1 a_{i+1} ai+1已知,那么只需要把 a i + 1 a_{i+1} ai+1中应该为1的位但不是1的改成1即可作为b,其它位都为0,这样必要的1有了,其它都为0,字典序最小
trick:
1.按位异或,一个重要的性质是,相同为0,即aa=0,从而得到abb=a,即a=abb,如果题目要求构造一个数使得该数和给定数a异或起来满足要求,可以先把满足要求的数x确定下来,然后需要构造的数y=xa(因为ay=x,所以aya=xa)
如果两者异或起来为1的话,那么两者不同,由于二进制只有0和1,所以两者其中一个是0,另一个是1,换句话说,两者刚好相反,互补
2.按位与,如果a & b =a,那么a中1所在的位,b也都是1
3.按位或,如果a中为1的位,b都想要为1,同时保留b中原有的1,那么b或上a,相当于将两者为1的位都并起来为x,如果p^q为x的话,除了两者都为0的位,其它位都互补
# 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;
for(int i=1;i<=n;i++) cin>>a[i];
b[1]=0;
for(int i=2;i<=n;i++){
int x=a[i-1]^b[i-1];
b[i]=(x|a[i])^a[i];///a[i]^b[i]=x|a[i]
}
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;
}
在1到n中选择一个数
两个人M和A博弈,对于随机整数c,谁选的数离c近,谁就赢,如果距离相同,M赢
M数字已知,要想让A赢的概率最大,A应该选择哪个数
根据样例,看M在左半边还是右半边
选择数字M-1或者M+1
M-1和n-(M+1)+1=n-M比,哪个大选哪个
坑点:
M-1和M+1超出选数的范围了
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
cin>>n>>m;
if(m-1>=n-m&&m-1>=1) cout<<m-1<<endl;
else if(m-1<n-m&&m+1<=n) cout<<m+1<<endl;
else cout<<m<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
1000分的题,但还是归到这来,因为这题和之前做过的题联系起来了,比较有重大意义
给定一个01串s
操作:删除一个字符,花费1金币或者免费交换一对字符
次数不限
使得ti不等于si对于整个t(可以为空)
问最少花费多少金币
两个一样的序列进行0和1的匹配,分别统计0和1的数量,从头开始遍历,一直到不能匹配
trick:
1.两两可以任意交换–可以随便排序
2.两两匹配问题
分为三种
第一种是序列内部进行两两匹配,比如说最小和最大匹配,次小和次大匹配
第二种是两个不同的序列进行匹配,比如说每次匹配差值最大的,要么序列a的最大和序列b的最小匹配,要么序列a的最小和序列b的最大匹配
第三种是两个一样的序列进行匹配,一般是通过自身元素的两两交换来满足某个要求,本质上就是自身和自身匹配
#include
#define endl '\n'
#define int long long
using namespace std;
string s;
void solve() {
cin>>s;
int n=s.size();
map<char,int>mp;
for(int i=0;i<n;i++) mp[s[i]]++;
for(int i=0;i<(int)s.size();i++){
if(s[i]=='1'){
if(mp['0']) mp['0']--;
else{
cout<<n-i<<endl;
return;
}
}
else{
if(mp['1']) mp['1']--;
else{
cout<<n-i<<endl;
return;
}
}
}
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;
}
add x表示在集合中添加一个2的x次方
get w问是否可以从集合中取得一些数,和为w
trick:
任何一个数都可以由若干个2的次幂组成有两种方法:
1.将该数转化成二进制,然后一位一位看,如果第x位为1的话,那么 就有 2 x 2^x 2x,具体操作方法可以参考快速幂
int cnt=0; while(x){ if(x&1) ans.push_back(cnt); x>>=1; cnt++; }
2.从高到低贪心
for(int i=29;i>=0;i--){ if(x>=(1ll<<i)){ ans.push_back(i); x-=(1ll<<i); } }
但是你会发现,每一位二进制数都只用一次,该题中每个2的次幂不止一个,实际上从大到小贪心和每个2的次幂只用一次的贪心本质上是一样的
如果某个2的次幂有多个的话,那么就只保留一个(有奇数个)或者 一个也不保留(有偶数个),然后其余的进行合成,往上进位
比如说有5个 2 3 2^3 23,想当于1个 2 5 2^5 25
再比如说6个 2 3 2^3 23,相当于1个 2 5 2^5 25以及1个 2 4 2^4 24
所以总能转化成每个2的次幂只用一次
所以从高到低进行贪心,如果能构成就能,不能就不能
#include
#define endl '\n'
#define int long long
using namespace std;
int m;
void solve() {
cin>>m;
map<int,int>mp;
while(m--){
int op,x;
cin>>op>>x;
if(op==1) mp[x]++;
else{
for(int i=29;i>=0;i--){
if(mp[i]){
x-=min(x/(1ll<<i),mp[i])*(1ll<<i);
}
}
if(x==0) 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表示想要制作多少种类型的冰淇淋(exactly)
两个球做成一个冰淇淋
问最少需要买几个球(答案总是存在)
之前做过了
二分求出C(x,2)小于等于n,x为不同类型球的数量,然后剩下的补足买之前已经买过的
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
int C2(int x){
return x*(x-1)/2;
}
void solve() {
cin>>n;
int l=1,r=1e10;
while(l<r){
int mid=(l+r+1)/2;
if(C2(mid)>n) r=mid-1;
else l=mid;
}
if(C2(l)==n) cout<<l<<endl;
else cout<<l+n-C2(l)<<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个物品,权重为wi
背包容量为W
其实审题这一步很关键,最有用的办法就是一边读题一边用自己的话记录,一边理解一边想
将物品放入背包,使得重量大于等于背包容量的一半,输出一种方案,无解输出-1
发现贪心可行
从大往小贪,只要可以放就放,一旦超过了总容量的一半就可以结束了,如果直到最后都没有超过总容量的一半,就无解
证明:从大到小遍历,首先如果第一个就超过总容量的一半,那么直接结束,否则,就看下一个,由于大的都没有超过总容量的一半,再加一个小的一定不会超过总容量,假设加起来超过总容量的一半,那么直接结束,否则同理,再往下加也不会超过总容量的
trick:
1.其实审题这一步很关键,最有用的办法就是一边读题一边用自己的话记录,一边理解一边想
2.当脑子里立马蹦出个思路,特别是贪心,一定要造不同情况的复杂的极端的样例进行验证
3.如果排序后仍需要知道下标,可以将pair类型存入vector,这比结构体方便
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
int n,w;
void solve() {
cin>>n>>w;
vector<PII>ans;
for(int i=1;i<=n;i++){
int x;
cin>>x;
ans.push_back({x,i});
}
sort(ans.begin(),ans.end());
reverse(ans.begin(),ans.end());
int sum=0;
vector<int>res;
for(int i=0;i<(int)ans.size();i++){
if(w>=ans[i].first){
sum+=ans[i].first;
res.push_back(ans[i].second);
if(sum>=(w+1)/2){
cout<<res.size()<<endl;
for(auto v:res) cout<<v<<' ';
cout<<endl;
return;
}
}
}
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(数[0,1e9])
操作:选择两个数,如果两个数的和为奇数,那么让后一个数等于前一个数,如果和为偶数,那么让前一个数等于后一个数
最多n次–>尽可能都用完
使序列非降序,一定有解–>万能通用的方法
操作的对象为两个,可以让一个定死,也可以让两个都定死
有一个不变的点就是里面的数不能凭空出现里面没出现过的数
如果第一个数是偶数的话,那么先让所有偶数都变相同,然后所有的奇数都变得和第一个数一样
如果第一个数是奇数的话,同理
trick:
1.操作的对象为两个,可以让一个定死,也可以让两个都定死
2.有关数学(奇偶数,质数,合数,因数,只要是关于数的)以及构造的,都往奇偶分位以及奇数偶数方向分类想
#include
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
int a[N];
int n;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
vector<PII>ans;
if(a[1]%2==0){
int idx=-1;
for(int i=n;i>=1;i--){
if(a[i]%2==0){
idx=i;
break;
}
}
if(idx!=1){
for(int i=1;i<=n;i++){
if(i==idx) break;
if(a[i]%2==0) ans.push_back({i,idx});
}
}
for(int i=2;i<=n;i++){
if(a[i]%2) ans.push_back({1,i});
}
}
else{
int idx=-1;
for(int i=n;i>=1;i--){
if(a[i]%2){
idx=i;
break;
}
}
if(idx!=1){
for(int i=1;i<=n;i++){
if(i==idx) break;
if(a[i]%2) ans.push_back({i,idx});
}
}
for(int i=2;i<=n;i++){
if(a[i]%2==0) ans.push_back({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的数组a(均为正负1)
分段,每个分段的计数方法为加减交替,第一个数为加
满足所有分段加起来为0
如果n为奇数,无解
n为偶数,一定有解,前后两个要么一样,要么不一样,如果一样的话,就放在一段,如果不一样,就分别一段
#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;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
if(n%2==1){
cout<<-1<<endl;
return;
}
vector<PII>ans;
for(int i=1;i<=n;i+=2){
if(a[i]==a[i+1]) ans.push_back({i,i+1});
else ans.push_back({i,i}),ans.push_back({i+1,i+1});
}
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的数组a(数[1,2e5])
构造两个全排列,使得max(pi,qi)=ai,可能无解
ai并不是全排列
ai确定之后,那么pi和qi都必须小于等于ai,然后其中一个等于ai,另一个小于等于ai
其中一个等于ai后,如果后缀中没有ai了,那么另一个也可以等于ai
首先,将p中还没放置的数都放在set1中,将q中没放置的数都放在set2中,如果当前需要的数不在set1中,那么就需要q来放,如果当前的数在后面没有出现过,那么就p和q放一样的数
最后没有放的位置进行补数,当然得放比ai小的数,可以利用set自带的二分,最后检验
trick:
利用set的二分时,返回迭代器,进行迭代器的加减时特别注意是否越界
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int p[N],q[N];
int n;
void solve() {
cin>>n;
memset(p,0,sizeof p);
memset(q,0,sizeof q);
map<int,int>mp,mmp;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
set<int>s1,s2;
for(int i=1;i<=n;i++) s1.insert(i),s2.insert(i);
for(int i=1;i<=n;i++){
mmp[a[i]]++;
if(s1.count(a[i])){
p[i]=a[i];
s1.erase(a[i]);
if(mmp[a[i]]==mp[a[i]]&&s2.count(a[i])) q[i]=a[i],s2.erase(a[i]);
}
else if(s2.count(a[i])){
q[i]=a[i];
s2.erase(a[i]);
}
}
for(int i=1;i<=n;i++){
if(p[i]) continue;
auto it=s1.lower_bound(a[i]);
if(it==s1.begin()){
cout<<"No"<<endl;
return;
}
it--;
p[i]=*it;
s1.erase(it);
}
for(int i=1;i<=n;i++){
if(q[i]) continue;
auto it=s2.lower_bound(a[i]);
if(it==s2.begin()){
cout<<"No"<<endl;
return;
}
it--;
q[i]=*it;
s2.erase(it);
}
for(int i=1;i<=n;i++){
if(max(p[i],q[i])!=a[i]){
cout<<"No"<<endl;
return;
}
}
cout<<"Yes"<<endl;
for(int i=1;i<=n;i++) cout<<p[i]<<' ';
cout<<endl;
for(int i=1;i<=n;i++) cout<<q[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的排列,n在[2,2e3]
操作:n次操作,第i次操作将前i个数循环右移任意次数
问最后能否得到数组a,无解输出-1
输出位移总数最小的方案数
已知最终的结果,要求输出中间的过程->逆推
由于最终结果已知,第n个数是最后一次操作移动的,那么可以得到最后一次操作的最小循环右移次数,并推得最后一次操作前的序列,以此类推
trick:
1.已知最终的结果,要求输出中间的过程->逆推
2.用map预先记录每个数的位置,已知循环右移后的序列,可以快速得到循环右移的次数
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e3+10;
int a[N];
int ans[N];
int n;
void solve(){
cin>>n;
map<int,int>mp;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]=i;
for(int i=n;i>=1;i--){
int res=mp[i]%i;//循环右移的次数
ans[i-1]=res;
//将1到i循环左移res回到上一状态,相当于循环右移i-res
for(int j=1;j<=i;j++){
mp[j]=(mp[j]+i-res)%i;
}
}
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 * m的表格 (n和m均小于等于100)
均为0和1
数据小,考虑暴力
进行着色,0表示白色,1表示黑色
一开始全是0,即全是白色
操作:选择子矩形,将其变成国际象棋的颜色(左上角为白色)
输出着色方案,无解输出-1
最左上角的格子必须是0,否则直接-1
从后往前操作,用1 * 2的矩阵去涂色
坑点:
想到从后往前用1 * 2的矩阵,但是没想到可以用竖着的1 * 2矩阵
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
char s[N][N];
int n,m;
struct node{
int x1,y1,x2,y2;
};
void solve() {
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>s[i][j];
}
}
if(s[1][1]=='1'){
cout<<-1<<endl;
return;
}
vector<node>ans;
for(int i=n;i>=1;i--){
for(int j=m;j>=1;j--){
if(s[i][j]=='1'&&j>=2) ans.push_back({i,j-1,i,j});
else if(s[i][j]=='1'&&j==1) ans.push_back({i-1,j,i,j});
}
}
cout<<ans.size()<<endl;
for(auto v:ans) cout<<v.x1<<' '<<v.y1<<' '<<v.x2<<' '<<v.y2<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
目标:将x减为1
可以减x的因子,但是同一个数最多只能减两次
最多需要减1000次,一定有解
由于任何一个数都都可以由若干个2的幂次构成
转化成2进制
先减到2的幂次,然后就好办了
#include
#define endl '\n'
#define int long long
using namespace std;
int x;
void solve() {
cin>>x;
string s;
int a=x;
int b=x;
while(a){
s=(char)((a&1)+'0')+s;
a>>=1;
}
vector<int>ans;
int len=s.size();
//将x变为2的幂次
for(int i=len-1;i>=1;i--){
if(s[i]=='1') {
ans.push_back(1ll<<(len-1-i));
x-=(1ll<<(len-1-i));
}
}
while(x!=1){
ans.push_back(x/2);
x/=2;
}
cout<<ans.size()+1<<endl;
cout<<b<<' ';
for(auto v:ans) cout<<b-v<<' ',b-=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的排列
操作:共n次操作,第i次操作选择一个非空后缀,将它们均加i(可以对同一后缀操作很多次)
使得逆序对数量最少
从大到小,整个后缀同时加应该是最优的,因为一下子就增加这么多的正序对
但是交上去错了,这样并不是最优的
发现我们完全可以让逆序对的数量为0
比如说5 3 2 4 1
我们可以让[2,5]都加5,即在第5次操作[2,5],在第3次操作[3,5],在第2次操作[4,5],在第4次操作[5,5]
比较巧妙,当作案例
trick:
两个相邻的数(均为正),要想让小的数大于大的数,让小数加上大的数
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int ans[N];
int n;
void solve() {
cin>>n;
memset(ans,0,sizeof ans);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=2;i<=n;i++) ans[a[i-1]]=i;
for(int i=1;i<=n;i++){
if(ans[i]) cout<<ans[i]<<' ';
else cout<<1<<' ';
}
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;
}
构造一个序列,使得从下标2到n中,如果两个下标互质,那么两数不相等
使得序列的最大值最小,无解输出-1
相差为1的两个数互质
两个质数互质
所有质数都互质
所以全部偶数都放1,然后如果是质数的话,那么就依次放不同的数
如果是合数的话,那么就找它的因数,然后和它的因数放一样的答案
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int n;
int ans[N];
bool st[N];
int prime[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;
get_prime(n);
ans[2]=1;
int idx=1;
for(int i=3;i<=n;i++){
if(!st[i]) ans[i]=++idx;
else{
for(int j=2;j<=i/j;j++){
if(i%j==0){
ans[i]=ans[j];
break;
}
}
}
}
for(int i=2;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的数组c
问是否有两个不同的子序列,f(a)= f(b),f是将所有数或
只要索引不完全一样,子序列就不同
或:有1出1
如果某个数该位上就它为1,那么称这个数是必要的
如果所有数都是必要的,那么不可能有两个不同的子序列,f(a)= f(b),f是将所有数或
否则可以令a为整个序列,b为在a中剔除一个非必要的
trick:
1.如果想看某位是不是就一个数为1,可以用map桶计数,来记录有多少个数在该位上为12.位运算一般的技巧是将所有数都位运算
3.或是有1出1,只要数a某位上已经为1了,那么只要或上了a,那么该位一定为1,如果b该位也为1,其它位为0,那么或上b结果不变,就称b是非必要的
如果某位就数c为1的话,那么就称c是必要的
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin>>n;
vector<vector<int>>e(n+1);
map<int,int>mp;
for(int i=0;i<n;i++){
int k;
cin>>k;
for(int j=0;j<k;j++){
int x;
cin>>x;
e[i].push_back(x);
mp[x]++;
}
}
int cnt=0;
for(int i=0;i<n;i++){
for(int j=0;j<(int)e[i].size();j++){
if(mp[e[i][j]]==1){
cnt++;
break;
}
}
}
if(cnt==n) cout<<"No"<<endl;
else 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;
}
依次枚举,如果当前为0的话,然后看已经几个连续的0就需要连续靠0赢几次,同理,看几个连续的1就靠1赢几次
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int n;
int ans[N];
string s;
void solve() {
cin>>n;
cin>>s;
s=' '+s;
int cnt_0=0,cnt_1=0;
for(int i=1;i<=n-1;i++){
if(s[i]=='0'){//当前为0,看有几个连续的0
cnt_0++;
ans[i+1]=i+1-cnt_0;
cnt_1=0;
}
else {
cnt_1++;
ans[i+1]=i+1-cnt_1;
cnt_0=0;
}
}
for(int i=2;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串
数据比较小,考虑暴力
好字符串:所有对称的字符均不相等
操作:在任意位置插入01
无解输出-1,有解肯定在300次操作以内
暴力模拟
trick:
string的insert函数:s.insert(pos,tmp) 将tmp字符串插入在下标为pos的前面
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
cin>>n;
cin>>s;
vector<int>ans;
int cnt=0;//操作了几次
//对称的下标相加为s.size()-1
for(int i=0;i<(int)s.size();i++){
if(s[i]==s[(int)s.size()-1-i]){
if(i==0){
string tmp="01";
if(s[0]=='1') s=tmp+s,ans.push_back(0),i=0;
else ans.push_back((int)s.size()),s+=tmp;
}
else{
if(s[i]=='0'){
int pos=(int)s.size()-1-i;//在pos后面插入01
string tmp="01";
s.insert(pos+1,tmp);
ans.push_back(pos+1);
}
else{
int pos=i;//在pos前面插入01
string tmp="01";
s.insert(pos,tmp);
ans.push_back(pos);
i-=2;
}
}
cnt++;
if(cnt>300) break;
}
}
for(int i=0,j=(int)s.size()-1;i<=j;i++,j--){
if(s[i]==s[j]){
cout<<-1<<endl;
return;
}
}
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;
}
1到n共n个数
从中最多选取k个数,使得选取的所有数异或和最大
最大的数为n,假设n转化成二进制一共cnt位,异或是按位异或,不可能可以进位,所以最多也就cnt位,那么最大也就是cnt位全都为1
那么只要选两个数,一个数为n,另一个数只要把n为0的位补成1就行了
trick:
位运算是按位的,对于参与位运算的若干数,首先最大的那个数转化成二进制后最左边那位肯定是1(忽略前导0),假设共cnt位,按位运算是不可能进位的,所以这些数进行位运算最大也就是cnt位全部为1
#include
#define endl '\n'
#define int long long
using namespace std;
int n,k;
void solve() {
cin>>n>>k;
int ans=0;
int num=1;
if(k==1){
cout<<n<<endl;
return;
}
while(n){
ans^=num;
num<<=1;
n>>=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;
}
长度为n的01序列
操作:n次操作,第i次操作在0到i-1之间选择一个整数p,在序列b的p位置后插入0,并反转它前面所有元素
最后一个必须是0,否则无解
从后往前,数连续1的个数cnt,可以先产生cnt个0,然后将cnt个0反转为1
trick:
1.操作是插入一个0并将前缀反转,并不会影响后面的序列,考虑从后往前
2.01反转,考虑一段一段,连续cnt个1可以将连续cnt个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;
for(int i=1;i<=n;i++) cin>>a[i];
if(a[n]==1){
cout<<"NO"<<endl;
return;
}
cout<<"YES"<<endl;
int cnt=0;
for(int i=n-1;i>=0;i--){
if(a[i]==1){
cnt++;
cout<<0<<' ';
}
else{
cout<<cnt<<' ';
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;
}
构造长度为a+b的01串,刚好a个0,b个1
刚好有x对相邻对不相等
一定有解
#include
#define endl '\n'
#define int long long
using namespace std;
int a,b,x;
void solve() {
cin>>a>>b>>x;
if(a<=b){//1比较多
if(x%2==1){
for(int i=0;i<x/2;i++) cout<<"10";
for(int i=0;i<b-x/2;i++) cout<<1;
for(int i=0;i<a-x/2;i++) cout<<0;
cout<<endl;
}
else{
for(int i=0;i<x/2;i++) cout<<"10";
for(int i=0;i<a-x/2;i++) cout<<0;
for(int i=0;i<b-x/2;i++) cout<<1;
cout<<endl;
}
}
else{
if(x%2==1){
for(int i=0;i<x/2;i++) cout<<"01";
for(int i=0;i<a-x/2;i++) cout<<0;
for(int i=0;i<b-x/2;i++) cout<<1;
cout<<endl;
}
else{
for(int i=0;i<x/2;i++) cout<<"01";
for(int i=0;i<b-x/2;i++) cout<<1;
for(int i=0;i<a-x/2;i++) cout<<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个硬币
将n枚硬币分成若干组,使得1到n这些数都可以由某几组构成
求最少分成几组
要使组数最少,那么每组硬币数可以跨度大一些,想到2的幂次
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin>>n;
int ans=0;
for(int i=0;;i++){
int x=(1ll<<i);
if(n>=x){
ans++;
n-=x;
}
else break;
}
if(n) ans++;
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(数[-1e9,1e9])每个数均不同
操作:
首先在末尾加入一个没出现过的数,选择一个正整数x
然后每次操作将x加到其中一个元素上
目标是让所有元素相等,问最少操作几次
先排个序
只能变大,不能变小,那么就都变成最大的那个
要使操作次数少,那么每次加的数要大一些,所有其它数和最大值的差值的gcd即为x,得到x之后就很简单了
那么有没有可能a[n+1]作为最大的数,然后其它n个数和a[n+1]的差值全部取gcd会更大呢?
不可能!!!
trick:
直接理解记忆这张图
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int n;
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
void solve() {
cin>>n;
map<int,int>mp;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;
if(n==1){
cout<<1<<endl;
return;
}
sort(a+1,a+1+n);
int x=0;
for(int i=1;i<=n-1;i++){
x=gcd(x,a[n]-a[i]);
}
int res=a[n];
while(mp[res]){
res-=x;
}
int ans=0;
for(int i=1;i<=n;i++){
ans+=(a[n]-a[i])/x;
}
ans+=(a[n]-res)/x;
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个顶点,m条边组成的无向图
求孤立顶点的最少和最多数量
最多:x个顶点连成完全无向图,所用边数大于等于m,然后剩下节点成为孤立节点
最少:两两顶点一对,用一条边,n-m*2
trick:
图论一定要有特判,n为1和m为0
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
int cal(int x){//x个顶点
return x*(x-1)/2;//成为完全无向图需要的边数
}
void solve() {
cin>>n>>m;
if(m==0){
cout<<n<<' '<<n<<endl;
return;
}
int l=1,r=n;
while(l<r){
int mid=(l+r)/2;
if(cal(mid)>=m) r=mid;
else l=mid+1;
}
cout<<max(0ll,n-m*2)<<' '<<max(0ll,n-l)<<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到m的排列
vi是第i列mex值
beauty:v1到vm的mex
要求使mex最大
构造矩阵
0 1 2 3
1 2 3 0
2 3 0 1
先构造m-1行
再0 1 2 3重复
答案为m+1
0 1 2 3
1 2 3 0
不够m-1行,答案为n+1
0 1 2 3 4
1 2 3 4 0
特判m为1的情况
trick:
1.构造矩阵,一边遍历一边输出,很少存到数组再输出的
2.构造矩阵,一般要特判n为1,m为1
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
cin>>n>>m;
if(m==1){
cout<<0<<endl;
for(int i=0;i<n;i++) cout<<0<<endl;
return;
}
if(n<m-1){
cout<<n+1<<endl;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cout<<(j+i)%m<<' ';
}
cout<<endl;
}
}
else{
cout<<m<<endl;
for(int i=0;i<m-1;i++){
for(int j=0;j<m;j++){
cout<<(j+i)%m<<' ';
}
cout<<endl;
}
for(int i=0;i<n-(m-1);i++){
for(int j=0;j<m;j++){
cout<<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串
操作:选择一位不反转,其余位均反转
一共操作k次,使得字典序最大
01反转看奇数次,偶数次,如果反转奇数次,那么反转,否则不变
尽可能把前面的0 变成1
总反转次数为k,该位的反转次数即为k减去该位上的数字
从前往后遍历,如果是1的话,就需要保持不变,如果k为奇数,那么该位先放个1,使得该位操作次数为偶数 ,如果k为偶数,那么该位不用放数字
如果是0的话,就需要反转,如果k为奇数,那么该位不用放数字,如果k为偶数 ,那么该位放个1,最后如果k还有剩余,那么就全放在最后一位
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int ans[N];
int n,k;
string s;
void solve() {
cin>>n>>k;
cin>>s;
memset(ans,0,sizeof ans);
int tmp=k;
for(int i=0;i<n;i++){
if(s[i]=='1'){
if(tmp&&k%2) ans[i]=1,tmp--;
else if(tmp&&k%2==0) ans[i]=0;
}
else{
if(tmp&&k%2) ans[i]=0;
else if(tmp&&k%2==0) ans[i]=1,tmp--;
}
if(!tmp) break;
}
ans[n-1]+=tmp;
for(int i=0;i<n;i++){
if((k-ans[i])%2){
if(s[i]=='1') s[i]='0';
else s[i]='1';
}
}
cout<<s<<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的字符串,由小写字母组成
回文计数:回文子串的个数
重新排列字符串,使得回文子串个数最多
aabbccddfgggghhh
16+2+1+1+1+1+6+3
aabb
abba
枚举了好几种情况,发现都是一样的排在一起是最多的
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
string s;
void solve() {
cin>>n;
cin>>s;
multiset<char>s1;
for(int i=0;i<n;i++) s1.insert(s[i]);
for(auto v:s1) 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个不同的整数在集合S中
Alice和Bob博弈
Alice先行
Alice在集合S中添加一个S中没出现过的正整数x([0,1e9])
Bob在集合S中移除一个数y,y必须严格小于S中的最后一个数
当Bob移除不了数或者Alice走了n+1步,游戏结束,结果为mex(S)
Alice使结果最大化,Bob使结果最小化
Alice先行,先补上mex
然后当Bob移除一个数,Alice就把那个数加回来
trick:
1.交互题,程序中需要输入的就正常输入,需要输出的就在每个输出语句后面加上一句cout.flush();
2.博弈题,如果按照最优策略的话,根据对方来出招,比如说我方为增加,对方为移除,我方增加肯定是有利于我方的,对方移除肯定是有利于对方的,可以当对方移除时,我们就加回来是一个很好的策略
#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;
map<int,int>mp;
for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]=1;
int x;//没有出现的第一个数
for(int i=0;i<n;i++){
if(!mp[i]){
x=-1;
break;
}
}
cout<<x<<endl;
fflush(stdout);
int y;
while(cin>>y){
if(y<0) break;
cout<<y<<endl;
fflush(stdout);
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
nn个实验室建在不同的高度,1到nn,高度依次递增
高处的实验室可以向低处的实验室输送水,一次输送一单位的水
将n*n个实验室分成n组,每组包含n个实验室
f(A,B):A组向B组输水,可以输多少单位的水
构造一种分组,使得任意两组的f,所有情况取min,尽可能大
从样例寻求到了一种构造方法:
1到n分别放在每一组中
n+1到2*n分别放到每一组中
以此类推
放的方式是正序,逆序,正序,逆序…
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin>>n;
int flag=1;
vector<vector<int>>e(n+1);
int cnt=0;
for(int i=1;i<=n;i++){//共n组
if(flag){//正序
for(int j=1;j<=n;j++){
e[j].push_back(++cnt);
}
}
else{
for(int j=n;j>=1;j--){
e[j].push_back(++cnt);
}
}
flag^=1;
}
for(int i=1;i<=n;i++){
for(auto v:e[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;
}
一共n堆鹅卵石
ai为第i堆鹅卵石的数量
构造用k种颜色给每颗鹅卵石上色
使得任意两堆鹅卵石中颜色c的数量最多差1个,要求k种颜色都满足,无解输出NO
平均分配
一遍一遍遍历,只要每组都还有石头可涂,那么每组涂一个(用同一种颜色)
只要有任何一组没有石头可涂,那么这组涂完之后(用同一种颜色),就换颜色
直到所有的石头都被涂上色
如果颜色不够了,就无解
数据比较小,直接暴力
#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;
int color=1;
int sum=0;
for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
vector<vector<int>>ans(n+1);
int cnt=0;//涂了几个石头
while(1){
bool ok=true;
for(int i=1;i<=n;i++){
if((int)ans[i].size()<a[i]){//还有石头可涂
ans[i].push_back(color);
cnt++;
}
else ok=false;
}
if(!ok) color++;
if(color>k||cnt==sum) break;
}
if(cnt<sum){
cout<<"NO"<<endl;
return;
}
cout<<"YES"<<endl;
for(int i=1;i<=n;i++){
for(auto v:ans[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;
}
长度为n的01串
beauty:[l,r]中0的个数和1的个数的乘积
给定m个区间
构造01串,使得m个区间美之和最大
和一定,差越小,积越大
0和1平均分配
#include
#define endl '\n'
#define int long long
using namespace std;
int n,m;
void solve() {
cin>>n>>m;
while(m--){
int l,r;
cin>>l>>r;
}
int flag=1;
for(int i=0;i<n;i++){
if(flag) cout<<1;
else cout<<0;
flag^=1;
}
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(数[-1000,1000])n大于等于2
排列,使得相邻两数差(前一个减后一个)之和最大,并且排列的字典序最小
最后的值为x1-xn,所以x1放最大,xn放最小,由于要字典序最小,中间升序
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=110;
int a[N];
int n;
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
reverse(a+1,a+1+n);
vector<int>ans;
for(int i=2;i<=n-1;i++) ans.push_back(a[i]);
sort(ans.begin(),ans.end());
cout<<a[1]<<' ';
for(int i=0;i<(int)ans.size();i++) cout<<ans[i]<<' ';
cout<<a[n]<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}
对于正整数x(数[1,1e6])
完美长猫数:转化为二进制数全为1
操作:A.将x和一个完美长猫数异或 B.+1
第奇数次操作A,第偶数次操作B
最多执行40次,一定有解
将x变成完美长猫数
和1异或相当于01反转
从高位到低位一位一位反转
但是要注意,加1可能会进位,导致前面的1变成0,所以每次都从最高位开始找
#include
#define endl '\n'
#define int long long
using namespace std;
int x;
void solve() {
cin>>x;
int xx=x;
int xxx=x;
int cnt=-1;//x转化为二进制最高位是第几位
while(xx){
cnt++;
xx>>=1;
}
vector<int>ans;
while(x&(x+1)){
for(int i=cnt;i>=0;i--){
if((x&(1ll<<i))==0){//如果该位是0的话,就反转为1
x^=((1ll<<(i+1))-1);
if(x&(x+1)) x++;
ans.push_back(i+1);
}
}
}
if(xxx%2==0) cout<<ans.size()*2-1<<endl;
else cout<<ans.size()*2<<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;
}
将1到n分到两个集合中,两个集合都不能为空,使得两组整数之和的差值最小
任意四个连续的数,x,x+1,x+2,x+3,可得x+x+3=x+1+x+2
所以每四个连续的数可以抵消,最后看n%4即可
trick:
任意四个连续的数,x,x+1,x+2,x+3,可得x+x+3=x+1+x+2
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin >> n;
int x = n % 4;
if (x == 1 || x == 2) cout << 1 << endl;
else cout << 0 << endl;
vector<int>ans;
for (int i = n; i >= 4; i -= 4) {
ans.push_back(i);
ans.push_back(i - 3);
}
if (x == 3) ans.push_back(3);
else if (x == 2) ans.push_back(2);
cout << ans.size() << ' ';
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的排列,0到n-1各出现一次
满足任意i,(ai+bi)%n = ci
无解输出-1
当n为奇数时,可以构造a为0到n-1,b为0到n-1
猜测n为偶数时无解
#include
#define endl '\n'
#define int long long
using namespace std;
int n;
void solve() {
cin>>n;
if(n%2){
for(int i=0;i<n;i++) cout<<i<<' ';
cout<<endl;
for(int i=0;i<n;i++) cout<<i<<' ';
cout<<endl;
for(int i=0;i<n;i++) cout<<i*2%n<<' ';
cout<<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名学生程序员,b名学生运动员,问最多有几人可以做到座位上,学生程序员不能连续,学生运动员不能连续
交替放置
优先放多的那个
取出一段一段连续的.
#include
#define endl '\n'
#define int long long
using namespace std;
int n,a,b;
string s;
void solve() {
cin>>n>>a>>b;
cin>>s;
if(a<b) swap(a,b);
vector<int>ans;//存放大于等于2的
int sum=0;//统计等于1的个数
int cnt=0;
for(int i=0;i<n;i++){
if(s[i]=='.') cnt++;
else{
if(cnt>=2) ans.push_back(cnt);
else if(cnt==1) sum++;
cnt=0;
}
}
if(cnt>=2) ans.push_back(cnt);
else if(cnt==1) sum++;
int res=0;
for(int i=0;i<(int)ans.size();i++){
int flag=1;
if(a<b) swap(a,b);//每次优先填大的
for(int j=0;j<ans[i];j++){
if(flag){
if(a){
res++;
a--;
}
}
else{
if(b){
res++;
b--;
}
}
flag^=1;
}
}
//为1的填什么都行
int res1=a+b;//剩下的
res+=min(res1,sum);
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;
}
一共有n个带颜色的罐子
ai表示颜色i罐子油漆的升数
从左到右给正方形涂颜料,一个正方形需要1升,必选按照x+1的循环顺序,起点颜料自定
问最多可以涂多少个正方形
取所有数的最小值minn,首先minn轮一定是可以的,然后就会出现很多个0造成阻断,那么只要统计连续非0个数最多是多少
#include
#define endl '\n'
#define int long long
using namespace std;
const int N=2e5+10;
int a[2*N];
int n;
void solve() {
cin>>n;
int ans=0;
int minn=2e9;
for(int i=1;i<=n;i++) cin>>a[i],minn=min(minn,a[i]);
for(int i=1;i<=n;i++) a[i]-=minn;
for(int i=n+1;i<=2*n;i++) a[i]=a[i-n];
ans+=minn*n;
int cnt=0;
int maxn=0;
for(int i=1;i<=2*n;i++){
if(a[i]) cnt++;
else{
maxn=max(maxn,cnt);
cnt=0;
}
}
if(cnt) maxn=max(maxn,cnt);
cout<<ans+maxn<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--) {
solve();
}
return 0;
}