【CF 706】(C.Hard problem) + (D.Vasiliy's Multiset) + (E.Working routine)【最短路、01字典树、十字链表模拟】

C. Hard problem

题意nn个字符串,顺序固定,每个字符串可以进行一次反转,反转代价为cic_i,现要求将所有字符串按照字典序排列,原有顺序不能改变,只能够进行反转,问最少需要多少代价可以让字符串按字典序排列。如果不能,输出1-1
思路:对于每一个字符串建立两个点,一个是原状态sis_i,一个是翻转状态sis_i'。如果sis_isi+1s_{i+1}小,则将两点连单向边,边权为00; 如果sis_isi+1s_{i+1}'小,则将两点连单向边,边权为ci+1c_{i+1}。然后对于sis_i'进行相同判断与连边操作。
最后建立一个源点连接s1s_1s1s_1',连接s1s_1'的边权为c1c_1,建立一个汇点,使sns_nsns_n'连接汇点,此处边权为00。从源点向汇点跑最短路,跑不到则输出1-1,否则输出答案即可。
反思:代码要注意细节,比赛的时候因为dijdij函数返回值为intint一直wawa在第1818个点,一直到比赛快结束了才找到问题,一定要引以为戒!
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 5*1e6+100;
const int M = 5*1e6+100;
// const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll inf = 5*1e16;
const db EPS = 1e-9;
using namespace std;

struct Edge{
   int to,next;
   ll w;
}e[M];
int head[N],tot,n,vis[N];
ll c[N],dis[N];

void add(int x,int y,ll w){
   e[++tot].to = y, e[tot].next = head[x], e[tot].w = w, head[x] = tot;
}

priority_queue< pair<ll,int> > q;

ll dijkstra(int s)
{
   while(q.size()) q.pop();
   memset(vis,0,sizeof vis);
   rep(i,0,2*n+2) dis[i] = inf;
   dis[s] = 0;
   q.push(make_pair(0ll,s));
   while(q.size())
   {
   	int x = q.top().second;
   	q.pop();
   	if(vis[x]) continue;
   	vis[x] = 1;	//只有从队列中弹出时才是更新好了这个点,此时才可以更新vis,不能在加入的时候就更新vis
   	for(int i = head[x]; i; i = e[i].next)
   	{
   		int y = e[i].to;
   		ll z = e[i].w;
   		if(dis[y] > (ll)(dis[x] + z)){
   			dis[y] = dis[x]+z;
   			q.push(make_pair(-dis[y],y));
   		}
   	}
   }
   if(dis[n*2+2] == inf) return -1;
   return dis[2*n+2];
}

int main()
{
   __; cin >> n; tot = 1;
   rep(i,1,n) cin >> c[i];
   string b1,b2;
   int r1,r2;
   rep(i,1,n){
   	string s1,s2;
   	cin >> s1; s2 = s1;
   	reverse(s2.begin(),s2.end());
   	if(i == 1){
   		b1 = s1, b2 = s2;
   		r1 = i, r2 = i+n;
   		add(2*n+1,r1,0);
   		add(2*n+1,r2,c[1]);
   	}
   	else{
   		if(b1 <= s1) add(r1,i,0ll);
   		if(b1 <= s2) add(r1,i+n,c[i]);
   		if(b2 <= s1) add(r2,i,0ll);
   		if(b2 <= s2) add(r2,i+n,c[i]);
   		b1 = s1, b2 = s2;
   		r1 = i, r2 = i+n;	
   	}
   }
   add(n,2*n+2,0);
   add(2*n,2*n+2,0);
   ll x1 = dijkstra(2*n+1);
   cout << x1 << endl;
   return 0;
}

D.Vasiliy’s Multiset

题意:维护一个MultisetMultiset,支持三种操作。① 加入一个数 ② 删除一个数 ③ 给出一个xx,在MultisetMultiset中找到一个yy,使得x xor yx\ xor\ y最大。注意,MultisetMultiset中初始存在00。(操作次数 2105\leq 2*10^5 ,数值 109\leq 10^9)
思路:可以发现本题的关键之处在于如何找到一个点yyxx的异或值最大,而异或值最大无非就是找到一个数,使得在二进制情况下,从最高位向最低位遍历,xx11处,yy00xx00处,yy11,位数越高优先级越高。
因此不难想到维护0101字典树,由于数值最大为10910^9,因此字典树深度最深为3232,即维护3232个二进制位。字典树中每个节点的flagflag记录这个节点被遍历了多少次,插入或删除一个元素,即将元素二进制状态下经过的节点 flagflag +11 / -11。最后考虑求与xx异或值最大的yy,将xx的二进制形式输入字典树,每一层尽量走与xx二进制位置不同的那个节点,最后能够找到一条路径,将路径上所有二进制位加起来,即得到了yy

【CF 706】(C.Hard problem) + (D.Vasiliy's Multiset) + (E.Working routine)【最短路、01字典树、十字链表模拟】

代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const int M = 1e5+100;
const int charset = 3;
const int max_node = 2*1e5*32+100;
const db EPS = 1e-9;
using namespace std;

int q;
char op[10];
int ss[40];

struct Trie
{
	int tot,root,child[max_node][charset];  //root:根节点  tot:节点编号
	//child[i][j]=k,表示编号为i的节点的第j个孩子是编号为k的节点
	int flag[max_node];   //是否以某一个字符为结束
	Trie()
	{
//		memset(child,0,sizeof(child));
		memset(flag,0,sizeof(flag)); 
		root = tot = 1;  //根节点编号为1 
	}	
	void mem()  //将字典树初始化 
	{
		memset(child,0,sizeof(child));
		memset(flag,0,sizeof(flag)); 
		root = tot = 1;  //根节点编号为1 
	}
	void dele()
	{
		int cur = root;
		for(int i = 1; i<=32; i++)
		{
			int x = ss[i];
			cur = child[cur][x];
			flag[cur]--;
		}
	}
	void insert()
	{
		int cur = root;
		for(int i = 1; i<=32; i++)
		{
			int x = ss[i];
			// LOG2("i",i,"x",x);
			// LOG1("cur_y",child[cur][1]);
			if(child[cur][x] == 0)
				child[cur][x] = ++tot;
			cur = child[cur][x];
			flag[cur]++;
		}
	}
	int query()
	{
		int tp = 0;
		int cur = root;
		for(int i = 1; i <= 32; i++)
		{
			int x = ss[i],y;
			if(x == 1) y = 0;
			else y = 1;
			// LOG3("i",i,"x",x,"y",y);
			// LOG1("cur_y",child[cur][y]);
			if(child[cur][y] != 0 && flag[child[cur][y]] >= 1){
				if(y == 1) tp += (1<<(32-i));
				cur = child[cur][y];
				// printf("in y\n");
			}
			else{
				if(x == 1) tp += (1<<(32-i));
				cur = child[cur][x];
				// printf("in x\n");
			}
		}
		return tp;
		//查询单词时应该 return flag[cur]; 
	}
}tre;

int main()
{
	scanf("%d",&q);
	memset(ss,0,sizeof ss);
	tre.insert();
	rep(i,1,q){
		scanf("%s",op);
		if(op[0] == '+'){
			int xx, pos = 32;
			scanf("%d",&xx);
			memset(ss,0,sizeof ss);
			while(xx){
				if(xx&1) ss[pos] = 1;
				xx /= 2;
				pos--;
			}
			// rep(i,1,32) printf("%d ",ss[i]);
			// printf(" ");
			tre.insert();
		}
		else if(op[0] == '-'){
			int xx, pos = 32;
			scanf("%d",&xx);
			memset(ss,0,sizeof ss);
			while(xx){
				if(xx&1) ss[pos] = 1;
				xx /= 2;
				pos--;
			}
			tre.dele();	
		}
		else{
			int xx, pos = 32, base;
			scanf("%d",&xx);
			base = xx;
			memset(ss,0,sizeof ss);
			while(xx){
				if(xx&1) ss[pos] = 1;
				xx /= 2;
				pos--;
			}
			int yy = tre.query();
			// LOG2("base",base,"yy",yy);
			ll ans = ((ll)yy)^((ll)base);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

E.Working routine

题意:给出一个nmn*m的矩阵,qq次操作,每次操作在矩阵中指定两个不重叠且不接触的小矩阵,将两个矩阵中的对应元素互换。在qq次操作之后,输出最后的结果矩阵。(2n,m103,1q104)(2\leq n,m\leq 10^3,1\leq q\leq 10^4)
思路:此题是子矩阵交换,我们可以先考虑一维状态下的子序列交换,区间[a,b][a,b][c,d][c,d]交换,如下图所示。每个节点存储右指针,只需将节点[a-1]与[c-1]以及[b]与[d]交换右指针即可。此处需注意,如果两个区间重叠或相互接触,则会发生错误,可以自行手动模拟一下。

【CF 706】(C.Hard problem) + (D.Vasiliy's Multiset) + (E.Working routine)【最短路、01字典树、十字链表模拟】

知道了一维情况下的操作,我们可以考虑二维情况下如何操作。首先每个节点需要维护向右与向下的指针,然后我们来考虑下图情况,两个子矩形相互交换元素。我们可以发现决定这个矩形具体所在位置只是A1、A3区域的向下指针与A2、A4区域的向右指针。
因此我们只需将A1、A3与B1、B3的向下指针进行交换,A2、A4与B2、B4的向右指针进行交换即可完成题目要求。注意如果两个矩形有可能相交或接触,则此算法不可行。

【CF 706】(C.Hard problem) + (D.Vasiliy's Multiset) + (E.Working routine)【最短路、01字典树、十字链表模拟】

代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 2*1e6+100;
const int M = 1e5+100;
const db EPS = 1e-9;
using namespace std;

int n,m,q;
struct Node{
	int r,d,v;
}t[N];

int Pos(int x,int y){
	x++, y++;
	return (x-1)*(m+1)+y;
}

int main()
{
	scanf("%d%d%d",&n,&m,&q);
	rep(i,1,n)
		rep(j,1,m) scanf("%d",&t[Pos(i,j)].v);
	//一共n+1行,m+1列,从(0,0)开始编号
	rep(i,0,n)
		rep(j,0,m){
			t[Pos(i,j)].r = Pos(i,j+1);
			t[Pos(i,j)].d = Pos(i+1,j);
		}
	rep(i,1,q){
		int a,b,c,d,h,w;
		scanf("%d%d%d%d%d%d",&a,&b,&c,&d,&h,&w);
		int x = 1,y = 1,u,v;
		rep(j,1,a-1) x = t[x].d;
		rep(j,1,b-1) x = t[x].r;
		rep(j,1,c-1) y = t[y].d;
		rep(j,1,d-1) y = t[y].r;
		u = x, v = y; //先往下再往右,左下周长部分
		rep(j,1,h){
			u = t[u].d, v = t[v].d;
			swap(t[u].r,t[v].r);
		}
		rep(j,1,w){
			u = t[u].r, v = t[v].r;
			swap(t[u].d,t[v].d);
		}
		u = x, v = y; //先往右再往下,右上周长部分
		rep(j,1,w){
			u = t[u].r, v = t[v].r;
			swap(t[u].d,t[v].d);
		}
		rep(j,1,h){
			u = t[u].d, v = t[v].d;
			swap(t[u].r,t[v].r);
		}
	}
	int x = 1;
	x = t[x].d, x = t[x].r;
	rep(i,1,n){
		int y = x;
		rep(j,1,m){
			printf("%d",t[y].v);
			y = t[y].r;
			if(j == m) printf("\n");
			else printf(" ");
		}
		x = t[x].d;
	}
	return 0;
}