#include<bits/stdc++.h>
using namespace std;const int max_s=2e5+10;
char s[max_s];//递归的思想//递归+回溯
int solve(int l, int r, char ch){if(l == r){if(s[l]== ch)return0;elsereturn1;}
int mid =(l + r)/2, left =0, right =0;for(int i = l; i <= mid; i++)if(s[i]== ch)
left++;for(int i = mid +1; i <= r; i++)if(s[i]== ch)
right++;
int len = r - l +1;
int min_s =min(len /2- left +solve(mid +1, r, ch +1), len /2- right +solve(l, mid, ch +1));return min_s;}
int main(){
int n;
cin >> n;while(n--){
int m;
cin >> m;for(int i =1; i <= m; i++)
cin >> s[i];
char ch ='a';
int min_s =solve(1, m, ch);
cout << min_s << endl;}return0;}
E题 Directing Edges
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int N=200009;
int tx, n, m, a[N], in_degree[N];
int t, x, y;
vector<pair<int,int>> ma[N];
vector<int> topo;voidtoposort(){
topo.clear();//拓扑序列初始化
queue<int> q;//存点的队列for(int i=1; i<=n; i++)//将入度为0的点放入队列中if(!in_degree[i])
q.push(i);//i即顶点,顶点入队//用size判空 while(q.size()){
x=q.front();
q.pop();
topo.pb(x);//将点放入拓扑序列中for(int i=0; i<ma[x].size(); i++){//将该点能到达的有向边全部删除(即可到达点的入度减1)//y是x->y,即x的后继
y=ma[x][i].X, t=ma[x][i].Y;//t=1有向边 //无向边在第一次的时候就先排查了,只判断有向边 if(t){//入度为0的顶点y入队 if(!--in_degree[y])
q.push(y);}}}}voidsolve(){//n是顶点数,m是边数
cin>>n>>m;for(int i=1; i<=n; i++){//初始化每个点的入度和图,点是从1开始的
in_degree[i]=0;
ma[i].clear();}while(m--){
cin>>t>>x>>y;//t=1,有向 if(t){//为有向边,记录x->y,并且标记为有向边,同时入度加1
ma[x].pb(mp(y,t));
in_degree[y]++;//入度边加一 }else{//为无向边,记录x->y,y->x,并且标记为无向边
ma[x].pb(mp(y,t));
ma[y].pb(mp(x,t));}}//拓扑排序 //按拓扑排序所记录输出,则一定是有向无环图//逆解!!! toposort();//无向边没有记录入度,如果给定的结构没有环,则YES//给定的结构有环,无解,给定的结构无环,有解输出 if(topo.size()!=n){//拓扑序列没有包含所有点,说明有环
cout<<"NO"<<endl;return;}else{
cout<<"YES"<<endl;//散列存储,a[i]=0,是第一个点,topo[x]=i//topo是一个vector,从0开始记录的 //a[i]记录的为topo的先后顺序 for(int i=0; i<n; i++)//记录拓扑序列的点的先后顺序
a[topo[i]]=i;for(int i=1; i<=n; i++){//从第一个开始输出所有的边//注意有向边只存储了一次,无向边存储了两次,处理时无向边的处理 for(int j=0; j<ma[i].size(); j++){
y=ma[i][j].X, t=ma[i][j].Y;//有向边,直接输出i->y if(t)//有向边直接输出
cout<<i<<" "<<y<<endl;else{//无向边按照在拓扑序列出现的先后顺序输入//拓扑排序中出现的序列,拓扑排序中只出现了一次 //保证了只输出一遍 if(a[i]<a[y])
cout<<i<<" "<<y<<endl;}}}}}
int main(){
cin>>tx;while(tx--)solve();return0;}
F题 Removing Leaves
#include<cstdio>
#include<iostream>
#include<cmath>
#include<set>
#include<vector>
using namespace std;//n是顶点数 k是每次去掉的数 ans是去掉的次数
int n, k, ans;//set是集合 不会包含重复的元素 有序的容器
vector<set<int>> g;
vector<set<int>> leaves;//自定义的比较函数,set中按照这个来排序 //重载()运算符
struct comp
{
bool operator()(int a, int b)const{//结点a和结点b的叶子数相等时,把结点小的放在前面 if(leaves[a].size()== leaves[b].size())return a < b;//不相等时,把叶子数多的放在前面 return leaves[a].size()> leaves[b].size();}};
int main(){//t组数据
int t;
cin >> t;while(t--){//n个顶点,n-1条边,一次删除k个
cin >> n >> k;//向量中有n个元素,元素类型为set<int>
g = leaves = vector<set<int>>(n);//n-1条边,输入需要n-1组数据 for(int i =0; i < n -1;++i){
int x, y;
cin >> x >> y;--x,--y;//无向,两边都要插入
g[x].insert(y);
g[y].insert(x);}//挨个查找顶点 for(int i =0; i < n;++i){//只有一个边,说明是叶子结点 if(g[i].size()==1){//插入到叶子结点//连接在1上的叶子结点
leaves[*g[i].begin()].insert(i);}}//自定义比较函数的set容器 //st的作用是排序,按照即将删除的顺序进行排序 set<int, comp> st;//在set中插入结点,按排序规则排序 for(int i =0; i < n;++i){
st.insert(i);}//ans是删除次数
int ans =0;while(true){//取第一个有叶子结点的结点(最小)
int v =*st.begin();//已经排好序列,拥有最大叶子数的顶点一定在最前面 //如果最大的结点都小于k,则一定输出0 //题意为输出确定的一个顶点都k个叶子,然后删除,即大于等于k //小于一次删除的数量 if(int(leaves[v].size())< k)break;//删除一次 for(int i =0; i < k;++i){//将第i个结点中的第一个元素取出来
int leaf =*leaves[v].begin();//删除第一个元素的边,因为无向,删除两个
g[leaf].erase(v);
g[v].erase(leaf);//将st中的两个删除
st.erase(v);
st.erase(leaf);//叶子结点中的leaf删掉
leaves[v].erase(leaf);//set中的count即返回集合中某个值的元素的个数,有返回1,没有返回0 //遇到a-b的情况,并且k的值为2,就需要将两个删除 if(leaves[leaf].count(v))
leaves[leaf].erase(v);//原来叶子连接的结点是否变成叶子结点//变成叶子结点则需要加入leaves if(g[v].size()==1){//to为删除结点(变成了叶子结点)的根节点
int to =*g[v].begin();//删除,目的是一会儿要重新排序
st.erase(to);//根节点多了一个叶子,叶子入集合中
leaves[to].insert(v);//重新插入排序
st.insert(to);//st.insert(v);}//v入set,排序 //主要的作用是将入set的v重新排序,写在if里面也是可以的
st.insert(v);//leaf入set,排在最后,size值为0
st.insert(leaf);}//次数加一
ans +=1;}//输出
cout << ans << endl;}return0;}
G题 Columns Swaps
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;const int maxn=2e5+100;
int n;
int a[maxn];
int b[maxn];
int visit[maxn];
vector<int> g[maxn];
vector<int> wjm;
int main(){
int t;scanf("%d",&t);while(t--){//n为列的个数 scanf("%d",&n);//清空+置0 for(int i=1;i<=n;i++)
g[i].clear();
wjm.clear();memset(visit,0,sizeof(visit));//输入数据,并压入vector中 //a[i]是第一行,b[i]是第二行 for(int i=1;i<=n;i++){scanf("%d",&a[i]);
g[a[i]].push_back(i);}for(int i=1;i<=n;i++){scanf("%d",&b[i]);
g[b[i]].push_back(i);}//标志
bool f=true;for(int i=1;i<=n;i++){//有不等于两个的,就不能组成排列//f标志置false,表示不成立,应输出-1 if(g[i].size()!=2)
f=false;}if(!f){printf("-1\n");continue;}//从元素1开始查找//大循环是为了形成环的情况考虑,需要多次验证,遍历每个元素 for(int i=1;i<=n;i++){//i和j是元素,k是列数 //表示已经拜访过此元素,不需要重新遍历 if(visit[i])continue;//x和y分别存储的是路径和遍历得到的废数据//一个数路径(交换),一个不是路径(不交换)//从上下两个出发,上面排好,下面排好,可以得到是最小的那个//x和y一个大于n/2,一个小于,也可以等于
vector<int> x,y;for(int j=i,k=g[i][0];!visit[j];){//遍历过的元素置1
visit[j]=1;//^是位运算符,相同为1,不同为0 //0异或任何数为任何数,1异或任何数为相反数//0^0=0,1^1=0,1^0=1,0^1=1 //k得到的是相同元素的不同列//比如1在第2和第4列,之前的k是2,异或后的k是4//g[j][0]和g[j][1]是元素j所处的列,原来是j的列,k是交换后元素的列
k=g[j][0]^g[j][1]^k;//如果元素在同一行,就交换,不在同一行上,就不交换//事实上并没有交换,只是模拟了交换的过程 if(a[k]==j&&g[j][0]!=g[j][1]){
x.push_back(k);
j=b[k];}else{
y.push_back(k);
j=a[k];}}//x.size()+y.size()=n//一定一个>=n/2,一个<=n/2//最小的一个是交换的次序,大的一个存储的是无用数字//插入,输出 if(x.size()<y.size())
wjm.insert(wjm.end(),x.begin(),x.end());else
wjm.insert(wjm.end(),y.begin(),y.end());}printf("%d\n",wjm.size());for(int i=0;i<wjm.size();i++)printf("%d ",wjm[i]);printf("\n");}return0;}