由于没有操作次数的限制,很容易想到对于任意 k 个相同的字符,他们都可以通过交换换到一起之后修改。而对于两个按照字母顺序排序之后相同的字符串,从第一个也可以经过若干交换换到第二个。
所以问题变成了:在两个字符序列中,一次可将 k 个相同的字母变为它的下一个,问是否能把第一个变成第二个。
注意数据,不能直接a,b进行排序。但是可以统计字母出现次数,然后从字母 a
开始到字母 z
,每次把第一个串中当前字母的个数,通过变成下一个字母,变得和第二个串的个数一样,如果不可行就输出 No
。这样就线性通过此题。
#include
using namespace std;
int a[100010];
void slove(){string s1,s2;int n,k;cin>>n>>k;cin>>s1>>s2;int a[30]={0},b[30]={0};for(auto ch:s1){a[ch-'a']++;}for(auto ch:s2){b[ch-'a']++;}for(int i=0;i<26;i++)if(a[i]>=b[i]){if((a[i]-b[i])%k!=0) {cout << "No" << '\n';return ;}elsea[i+1]+=a[i]-b[i];}else {cout << "No" << '\n';return ;}cout<<"Yes"<<'\n';
}int main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin>>t;while(t--){slove();}return 0;
}
这题本来是个蛮简单的题,但是审题没审清楚,没注意这是一个排列,一直在处理重复元素,hash,单调栈,map什么的都尝试过
很典的一个双指针,用两个变量,一个记录最大值一个记录最小值,然后双指针从左右两侧向中间靠,再讨论四种情况,不断更新左右最大值最小值
#include
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int a[N];
int n;
int main()
{cin.tie(0);cout.tie(0);ios::sync_with_stdio(false);int t;cin >> t;while (t--){cin >> n; for (int i = 1; i <= n; i++){cin >> a[i]; }int ma = n;int mi = 1;int l = 1, r = n;int ansl = 0, ansr = 0;while (r - l + 1 >= 4){bool flag = 0;//判断当前区间是否满足四种情况中的一种while (a[r] == ma && r - l + 1 >= 4){//右端点是最大值r--;//右端点左移ma--;//最大值-1flag = 1;}if (r - l + 1 < 4)break;while (a[r] == mi && r - l + 1 >= 4){//右端点是最小值r--;//右端点左移mi++;//最小值+1flag = 1;}if (r - l + 1 < 4)break;while (a[l] == ma && r - l + 1 >= 4){//左端点是最大值l++;//左端点右移ma--;//最大值-1flag = 1;}if (r - l + 1 < 4)break;while (a[l] == mi && r - l + 1 >= 4){//左端点是最小值mi++;//最小值+1l++;//左端点右移flag = 1;}if (!flag){//四种情况都没有发生ansl = l, ansr = r;break;}}if (!ansl){cout << -1 << endl;}else{cout << ansl << " " << ansr << endl;}}return 0;
}
先从s的后k项判断一下a的后面k-1项是不是满足递增的
然后数学推导一下会发现a的前n-k+1项,最大值就两种情况,s(n-k+1)/(n-k+1)或者s(n-k+1)/(n-k+1)+1,这里的/都是向下取整
#include
using namespace std;
typedef long long ll;
ll s[200010];
void slove(){int n,k;cin>>n>>k;ll pre=0;for(int i=1;i<=k;i++) {cin >> s[i];}pre=s[2]-s[1];for(int i=3;i<=k;i++){if(s[i]-s[i-1]cout<<"No"<<'\n';return;}elsepre=s[i]-s[i-1];}if(k==1) {cout << "Yes" << '\n';}else if(s[1]>=0){if(s[1]%(n-k+1)){if(s[1]/(n-k+1)+1<=s[2]-s[1])cout<<"Yes"<<'\n';elsecout<<"No"<<'\n';}else{if(s[1]/(n-k+1)<=s[2]-s[1])cout<<"Yes"<<'\n';elsecout<<"No"<<'\n';}}else{if(s[1]/(n-k+1)<=s[2]-s[1])cout<<"Yes"<<'\n';elsecout<<"No"<<'\n';}
}
int main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin>>t;while(t--){slove();}return 0;
}
又审题审错了,看成了每个si都要为0,实际上是si的和为0,1200的题都没做出。。。。。
#include
using namespace std;
#define pii pair
const int maxn = 200010;int n, a[maxn];
void solve() {cin>>n;for (int i = 1; i <= n; ++i) {cin>>a[i];}if (n & 1) {cout<<-1<<'\n';return;}vector 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<cout<ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin>>t;int cas = 1;while (t--) {solve();}return 0;
}
这题是上个题的困难版本,改动的条件是a数组不止由1和-1组成,还有可能有0
和上一个题其实相差也不多,可以考虑这么构造,相邻两个相等依旧选择把他们放到一个区间,不相等就判断是不是有0,如果没有0,那就分为两个区间,有0,再往后面看一位,若这个数和原来的那个非0数相等则将三个数放入一个区间,否则三个数构成三个区间。总而言之,这里面的0是可以忽略的,那么我们把0忽略后,就变成了上面那个简单版问题
同时还看到一个更巧妙的解法:
考虑分段的本质是什么,其实就是给每个元素乘上 11 或 −1−1,不能有两个相邻的元素都乘上 −1−1。
于是考虑,初始时假设每个元素都乘上了 11,现在我们需要将其中一些元素改为乘上 −1−1,修改的两个元素不能相邻。
直接贪心地选取即可,从前往后枚举每个元素,判断若当前元素乘上 −1−1,元素和的绝对值是否会减小,若减小就操作,然后记录一下,下一个元素不能再乘上 −1−1。
贪心的正确性也很好证明,若当前元素乘上 −1−1 后,元素和的绝对值会减小,但是我们不修改当前元素,那么下一个元素最多也只会让元素和的绝对值减小相同的数,所以我们先对当前元素进行修改,一定不会更劣,就做完了。
#include
using namespace std;
int wz[200005],n,a[200005],b[200005],x[200005],L[200005],R[200005];
int main(){int t;cin >> t;while(t--){int n;cin >> n;int cnt=0,ok=0,len=0;for (register int i=1;i<=n;i++){cin >> a[i];if (a[i]!=0) b[++cnt]=a[i],wz[cnt]=i;//记录每个非0位置的信息}if (cnt%2==1){cout << -1 << endl;//无解continue ;}for (register int i=1;i<=cnt;i+=2){if (b[i]!=b[i+1]){//第二种情况for (register int j=wz[i-1]+1;j<=wz[i+1];j++){L[++len]=R[len]=j;//每段单独分}}else {if (wz[i]+1==wz[i+1]) L[++len]=wz[i-1]+1,R[len]=wz[i+1];//相邻就直接分,注意要从上个非0位置+1开始else {for (register int j=wz[i-1]+1;j<=wz[i];j++) L[++len]=R[len]=j;//i之前全部单独分for (register int j=wz[i]+1;j<=wz[i+1]-2;j++) L[++len]=R[len]=j;L[++len]=wz[i+1]-1,R[len]=wz[i+1];//给第i+1个非零位置前留下一个0使它在区间里的长度变为偶数}}}if (R[len]L[len+1]=R[len]+1,R[len+1]=n;len++;//可能最后一个非零数后面还有,单独再开一段}cout << len << '\n';for (register int i=1;i<=len;i++) cout << L[i] << ' ' << R[i] << endl;}
}
#include
# define sum(x,y) (s[y]-s[x-1])
using namespace std;
inline int rd(){int f=1,s=0;char c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c<='9'&&c>='0'){s=(s<<3)+(s<<1)+(c^48);c=getchar();}return s*f;
}
const int N = 2e5;
int t,n,s[N+5],f[N+5],g[N+5],a[N+5];
int main(){t=rd();while(t--){n=rd();int sum=0,cnt=n;for(int i=1;i<=n;++i)a[i]=rd(),sum+=a[i],g[i]=0;for(int i=2;i<=n;++i)if(abs(sum-2*a[i])printf("%d\n",cnt);g[n+1]=0;for(int i=1;i<=n;++i){if(g[i])continue;if(g[i+1])printf("%d %d\n",i,i+1);else printf("%d %d\n",i,i); } }}return 0;
}
一个图论的构造题,先建图把优先级确定,让真子集指向父集合,初始可以把集合1到n初始值都赋为自己的下标i,如集合3初始值3,这样就能避免重复了,接着这就是一个标准的拓扑排序的题了,只不过在拓扑排序的过程中我们要进行集合加法,这个用set可以很容易解决
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 110;
int ind[maxn];
void slove() {int n, x;cin >> n;vectorg[maxn];vector>ans(maxn);for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++) {scanf("%1d", &x);if (x)ind[j]++, g[i].push_back(j);}for(int i=1;i<=n;i++)ans[i].insert(i);queueq;for (int i = 1; i <= n; i++)if (ind[i]==0)q.push(i);while (q.size()) {int u = q.front();q.pop();for (int i = 0; i < g[u].size(); i++) {if ((--ind[g[u][i]]) == 0) {q.push(g[u][i]);}for(auto it:ans[u])ans[g[u][i]].insert(it);}}for (int i = 1; i <= n; i++) {cout<ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin >> t;while (t--) {slove();}return 0;
}
构造方法:根据等差数列求和公式可以判断构造是否存在,以及第一项的值a,接着第一项选取1和a-1,然后选取3,a-1,也就是第一项加2,第二项-1,这样就可以等差数列的条件了,当第一个数大于等于第二个数时停止上诉操作,然后选取第一项为2,重复第一项+2,第二项-1的操作,可以数学证明,能够取完所有的数
#include
using namespace std;
const int N = 1e3 + 5;
typedef long long ll;
int main()
{cin.tie(0);cout.tie(0);ios::sync_with_stdio(false);int t;cin >> t;while (t--){ll n;cin >> n;if (n & 1){cout << "YES" << endl;ll b = 2 * n - n / 2;//s/n-n/2-1ll a = 1;for (int i = 1; i <= (n + 1) / 2; i++){//遍历n范围内的所有奇数,构成第1个到中间的数对cout << a << " " << b << endl;a += 2;b--;}a = 2, b = 2 * n;//2和s/n+1-2for (int i = 1; i <= n / 2; i++){//遍历n范围内所有偶数cout << a << " " << b << endl;a += 2;b--;}}else{cout << "NO" << endl;}}return 0;
}
因为只能交换子树,所以可以从叶子节点往上走,每次都更新子树中的数的左右边界,如果两个子树的区间出现了交叉,那么必然不可以通过交换满足题目要求,反之就判断谁在前谁在后,也就是是否需要交换
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=3e5+10;
struct {int l=INT_MAX,r=0;
}tree[maxn*2];
int m,ans=0,flag=0;
void dfs(int u){if(u>=m||flag)return;dfs(u*2);dfs(u*2+1);if(tree[u*2].l<=tree[u*2+1].r&&tree[u*2].r>=tree[u*2+1].l||tree[u*2+1].l<=tree[u*2].r&&tree[u*2+1].r>=tree[u*2].l)flag=1;else if(tree[u*2].l>=tree[u*2+1].r)ans++;
}
void slove(){cin >> m;for(int i=m;i<2*m;i++)cin>>tree[i].l,tree[i].r=tree[i].l;for(int i=m;i<2*m;i+=2){int a=i/2;while(a){tree[a].l=min(tree[a*2].l,tree[a*2+1].l),tree[a].r=max(tree[a*2].r,tree[a*2+1].r);a/=2;}}dfs(1);if(flag)cout<<-1<<'\n';elsecout<ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t;cin >> t;while (t--) {slove();flag=0;ans=0;}return 0;
}