CF1156G Optimizer 题解

好玩!

感觉也没到黑题难度吧。

思路很简单,但我代码写的太复杂了……不建议查看。

考虑一步步优化。

以下默认“赋值语句”同时表示第一类和第二类程序语句,和“语句”意思一样。

由于 string 类型修改,命名等太麻烦了,先考虑让 string 指向一个下标,然后运算好了再从下标调出字符串名字输出。这样同时也就支持了后面要用到“将最终的变量统一重命名为 res”。

首先,对于没用的变量,考虑把相关语句直接删掉。对于一个语句,如果它所赋值的东西在下一次再被赋值之前都没有用来赋值其它变量,也可以直接删掉。

从最后一次 res 被赋值反向遍历,遍历到的就是有用的语句,保存即可。

这时候的代码:

#include<bits/stdc++.h>
using namespace std;
namespace estidi{
	struct code{
		int tp,to,x,y,used;
		char op;
	}cd[3003];
	map<string,int>varl;
	map<unsigned long long,int>ap;
	int vcnt,lastver[3003];
	string names[3003];
	basic_string<int>sons[3003];
	unsigned long long varv[3003];
	int main(){
		int n;
		string s,var;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			cin>>s;
			unsigned long long eqpos=s.find('='),oppos=(s.find('$')&s.find('^')&s.find('#')&s.find('&'));
			if(oppos==string::npos){
				cd[i].tp=1;
				var=s.substr(0,eqpos);
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].to=varl[var];
				var=s.substr(eqpos+1);
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].x=varl[var];
			}
			else{
				cd[i].tp=2;
				var=s.substr(0,eqpos);
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].to=varl[var];
				var=s.substr(eqpos+1,oppos-eqpos-1);
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].x=varl[var];
				var=s.substr(oppos+1);
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].y=varl[var];
				cd[i].op=s[oppos];
			}
		}
		for(int i=1;i<=n;i++){
			sons[i]+=lastver[cd[i].x];
			if(cd[i].tp==2)
				sons[i]+=lastver[cd[i].y];
			lastver[cd[i].to]=i;
		}
		if(!lastver[varl["res"]]){
			printf("0");
			return 0;
		}
		queue<int>q;
		q.push(lastver[varl["res"]]);
		while(q.size()){
			int x=q.front();
			q.pop();
			if(cd[x].used)
				continue;
			cd[x].used=1;
			for(int i=0;i<sons[x].size();i++)
				q.push(sons[x][i]);
		}
		int cnt=0;
		for(int i=1;i<=n;i++)
			cnt+=cd[i].used;
		printf("%d\n",cnt);
		for(int i=1;i<=n;i++)
			if(cd[i].used){
				printf("%s=%s",names[cd[i].to].c_str(),names[cd[i].x].c_str());
				if(cd[i].tp==2)
					printf("%c%s",cd[i].op,names[cd[i].y].c_str());
				printf("\n");
			}
		return 0;
	}
}
int main(){
	estidi::main();
	return 0;
}

此时发现“如果它所赋值的东西在下一次再被赋值之前都没有用来赋值其它变量”太麻烦了,并且不利于后续操作。

考虑在开头重命名,同一个变量在再次赋值时“重定向”至一个新的变量(名字取为四位合法随机字符串),以后用这个被再次赋值的变量赋值时也自动“重定向”至新的变量。

这时候的代码:

#include<bits/stdc++.h>
using namespace std;
namespace estidi{
	random_device R;
	mt19937 G(R());
	int rand(int l,int r){
		return uniform_int_distribution<int>(l,r)(G);
	}
	char rc(bool d){
		if(rand(1,31)<=5&&d)
			return rand('0','9');
		if(rand(1,2)==1)
			return rand('a','z');
		return rand('A','Z');
	}
	struct code{
		int tp,to,x,y,used;
		char op;
	}cd[3003];
	map<string,int>varl;
	map<string,string>red;//redirect
	map<unsigned long long,int>ap;
	int vcnt,lastver[3003];
	string names[3003];
	basic_string<int>sons[3003];
	unsigned long long varv[3003];
	inline string hsh(){
		string s="";
		s+=rc(0);
		s+=rc(1);
		s+=rc(1);
		s+=rc(1);
		return s;
	}
	int main(){
		int n;
		string s,var,nvar;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			cin>>s;
			unsigned long long eqpos=s.find('='),oppos=(s.find('$')&s.find('^')&s.find('#')&s.find('&'));
			if(oppos==string::npos){
				cd[i].tp=1;
				var=s.substr(0,eqpos);
				do{
					nvar=hsh();
				}while(varl.find(nvar)!=varl.end());
				red[var]=nvar;
				vcnt++;
				varl[nvar]=vcnt;
				names[vcnt]=nvar;
				cd[i].to=vcnt;
				var=s.substr(eqpos+1);
				if(red.find(var)!=red.end())
					var=red[var];
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].x=varl[var];
			}
			else{
				cd[i].tp=2;
				var=s.substr(0,eqpos);
				do{
					nvar=hsh();
				}while(varl.find(nvar)!=varl.end());
				red[var]=nvar;
				vcnt++;
				varl[nvar]=vcnt;
				names[vcnt]=nvar;
				cd[i].to=vcnt;
				var=s.substr(eqpos+1,oppos-eqpos-1);
				if(red.find(var)!=red.end())
					var=red[var];
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].x=varl[var];
				var=s.substr(oppos+1);
				if(red.find(var)!=red.end())
					var=red[var];
				if(varl.find(var)==varl.end()){
					vcnt++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].y=varl[var];
				cd[i].op=s[oppos];
			}
		}
		varl["res"]=varl[red["res"]];
		names[varl["res"]]="res";
		for(int i=1;i<=n;i++){
//			cerr<<i<<" "<<cd[i].to<<":\n";
//			cerr<<cd[i].x<<" "<<lastver[cd[i].x]<<endl;
//			cerr<<cd[i].y<<" "<<lastver[cd[i].y]<<endl;
			sons[i]+=lastver[cd[i].x];
			if(cd[i].tp==2)
				sons[i]+=lastver[cd[i].y];
			lastver[cd[i].to]=i;
		}
		if(!lastver[varl["res"]]){
			printf("0");
			return 0;
		}
		queue<int>q;
		q.push(lastver[varl["res"]]);
		while(q.size()){
			int x=q.front();
			q.pop();
			if(cd[x].used)
				continue;
			cd[x].used=1;
			for(int i=0;i<sons[x].size();i++)
				q.push(sons[x][i]);
		}
		int cnt=0;
		for(int i=1;i<=n;i++)
			cnt+=cd[i].used;
		printf("%d\n",cnt);
		for(int i=1;i<=n;i++)
			if(cd[i].used){
				printf("%s=%s",names[cd[i].to].c_str(),names[cd[i].x].c_str());
				if(cd[i].tp==2)
					printf("%c%s",cd[i].op,names[cd[i].y].c_str());
				printf("\n");
			}
		return 0;
	}
}
int main(){
	estidi::main();
	return 0;
}

此时只剩下“多个变量表达的值相同时只保留一个变量”这个问题了。这一步应放在“去除无用变量”之前,只是我思考过程如此。

注意到不一定只是第一类语句带来相同的变量,可能有这种情况:

4
c=aa#bb
d12=aa#bb
res=c^d12
tmp=aa$c

此时 cd12 仍然是相同的值。

考虑记录那些值的实际表达式,例如这两个都是 (aa#bb)。但数量太多记录不下,会变成 \(O(2^n)\)

对于第二类语句,考虑集合 hash。先给初始存在的变量赋一个值,然后写四个不易冲突、无交换律、无结合律、无分配律的函数。此处我写的函数是形如 \(f(a,b)=a\times X_f+b\times Y_f\) 的形式,我选的所有 \(X\)\(Y\) 都是大质数。

const unsigned long long p1=7937178589361740601ULL,p2=6830583729503927513ULL,p3=1749436285057276859ULL,p4=3068371816923478519ULL,p5=593848203485719349ULL,p6=9376712947658437897ULL,p7=4921632685793207897ULL,p8=2314678902130987661ULL;
unsigned long long doop(unsigned long long a,unsigned long long b,char op){
	if(op=='$')
		return a*p1+b*p2;
	if(op=='^')
		return a*p3+b*p4;
	if(op=='#')
		return a*p5+b*p6;
	if(op=='&')
		return a*p7+b*p8;
	cerr<<"wtf"<<op;//这行理论不会被执行
	abort();//这行理论不会被执行
}

然后看此前哈希值有无出现过。若出现,因为已经提前重命名同一个变量,所以就直接在此后“重定向”至那个元素,并标记这条条语句无效。

对于第一类语句,直接“重定向”,并标记这条条语句无效。

这里的“重定向”类似并查集,初始 redirect[i]=i,修改时 redirect[from]=redirect[to] 即可。

最后,由于要让最后一个变量叫 res,所以要改一下:

  • 如果最后一条是第一类语句,且是直接由一个所有赋值语句前有值的、不叫 res 的变量赋值过来的,那么要在最后加一条 res=那个值
    要不然例如单独一条 res=ares 被重定向至 a 了,就没有语句生效,且本来第一类语句就要标记条语句无效。

  • 否则,把代表 res 的变量直接改名为 res

不知道为何要放 \(O(n^2)\) 的数据,我写的应该是 \(O(n\log n)\) 的。

完整代码:

#include<bits/stdc++.h>
using namespace std;
namespace estidi{
	const unsigned long long p1=7937178589361740601ULL,p2=6830583729503927513ULL,p3=1749436285057276859ULL,p4=3068371816923478519ULL,p5=593848203485719349ULL,p6=9376712947658437897ULL,p7=4921632685793207897ULL,p8=2314678902130987661ULL;
	random_device R;
	mt19937 G(R());
	int rand(int l,int r){
		return uniform_int_distribution<int>(l,r)(G);
	}
	unsigned long long rand(){
		return uniform_int_distribution<unsigned long long>(1,ULLONG_MAX)(G);
	}
	char rc(bool d){
		if(rand(1,31)<=5&&d)
			return rand('0','9');
		if(rand(1,2)==1)
			return rand('a','z');
		return rand('A','Z');
	}
	unsigned long long doop(unsigned long long a,unsigned long long b,char op){
		if(op=='$')
			return a*p1+b*p2;
		if(op=='^')
			return a*p3+b*p4;
		if(op=='#')
			return a*p5+b*p6;
		if(op=='&')
			return a*p7+b*p8;
		cerr<<"wtf"<<op;
		abort();
	}
	struct code{
		int tp,to,x,y,used;
		char op;
	}cd[3003];
	map<string,int>varl;
	map<string,string>redstr;
	map<unsigned long long,int>ap;
	int vcnt,lastver[3003],init[3003],redid[3003];
	string names[3003];
	basic_string<int>sons[3003];
	unsigned long long varv[3003];
	inline string hsh(){
		string s="";
		s+=rc(0);
		s+=rc(1);
		s+=rc(1);
		s+=rc(1);
		return s;
	}
	int main(){
		int n;
		string s,var,nvar;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			cin>>s;
			unsigned long long eqpos=s.find('='),oppos=(s.find('$')&s.find('^')&s.find('#')&s.find('&'));
			if(oppos==string::npos){
				cd[i].tp=1;
				var=s.substr(eqpos+1);
				if(redstr.find(var)!=redstr.end())
					var=redstr[var];
				if(varl.find(var)==varl.end()){
					vcnt++;
					init[vcnt]++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].x=varl[var];
				var=s.substr(0,eqpos);
				do{
					nvar=hsh();
				}while(varl.find(nvar)!=varl.end());
				redstr[var]=nvar;
				vcnt++;
				varl[nvar]=vcnt;
				names[vcnt]=nvar;
				cd[i].to=vcnt;
			}
			else{
				cd[i].tp=2;
				var=s.substr(eqpos+1,oppos-eqpos-1);
				if(redstr.find(var)!=redstr.end())
					var=redstr[var];
				if(varl.find(var)==varl.end()){
					vcnt++;
					init[vcnt]++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].x=varl[var];
				var=s.substr(oppos+1);
				if(redstr.find(var)!=redstr.end())
					var=redstr[var];
				if(varl.find(var)==varl.end()){
					vcnt++;
					init[vcnt]++;
					varl[var]=vcnt;
					names[vcnt]=var;
				}
				cd[i].y=varl[var];
				var=s.substr(0,eqpos);
				do{
					nvar=hsh();
				}while(varl.find(nvar)!=varl.end());
				redstr[var]=nvar;
				vcnt++;
				varl[nvar]=vcnt;
				names[vcnt]=nvar;
				cd[i].to=vcnt;
				cd[i].op=s[oppos];
			}
		}
		for(int i=1;i<=vcnt;i++){
			varv[i]=rand();
			redid[i]=i;
		}
//		cerr<<"---\n";
//		for(int i=1;i<=n;i++){
//			printf("%s=%s",names[cd[i].to].c_str(),names[cd[i].x].c_str());
//			if(cd[i].tp==2)
//				printf("%c%s",cd[i].op,names[cd[i].y].c_str());
//			printf("\n");
//		}
		for(int i=1;i<=n;i++)
			if(cd[i].tp==1)
				redid[cd[i].to]=redid[cd[i].x];
			else{
				varv[cd[i].to]=doop(varv[cd[i].x],varv[cd[i].y],cd[i].op);
				if(ap.find(varv[cd[i].to])!=ap.end()){
					redid[cd[i].to]=redid[ap[varv[cd[i].to]]];
					cd[i].used=-1;
				}
				else
					ap[varv[cd[i].to]]=cd[i].to;
			}
		for(int i=1;i<=n;i++)
			if(!cd[i].used){
				cd[i].to=redid[cd[i].to];
				cd[i].y=redid[cd[i].y];
				cd[i].x=redid[cd[i].x];
			}
		for(int i=1;i<=n;i++){
//			cerr<<i<<" "<<cd[i].to<<":\n";
//			cerr<<cd[i].x<<" "<<lastver[cd[i].x]<<endl;
//			cerr<<cd[i].y<<" "<<lastver[cd[i].y]<<endl;
			sons[i]+=lastver[cd[i].x];
			if(cd[i].tp==2)
				sons[i]+=lastver[cd[i].y];
			lastver[cd[i].to]=i;
		}
//		cerr<<names[redid[varl[redstr["res"]]]]<<endl;
		queue<int>q;
		q.push(lastver[redid[varl[redstr["res"]]]]);
		while(q.size()){
			int x=q.front();
			q.pop();
			if(cd[x].used)
				continue;
			cd[x].used=1;
			for(int i=0;i<sons[x].size();i++)
				q.push(sons[x][i]);
		}
		for(int i=1;i<=n;i++)
			if(cd[i].tp==1)
				cd[i].used=0;
		int cnt=0;
		if(names[redid[varl[redstr["res"]]]]!="res"){
			if(init[redid[varl[redstr["res"]]]])
				cnt++;
			else{
				varl["res"]=redid[varl[redstr["res"]]];
				names[varl["res"]]="res";
			}
		}
		for(int i=1;i<=n;i++)
			if(cd[i].used==1)
				cnt++;
		printf("%d\n",cnt);
		for(int i=1;i<=n;i++)
			if(cd[i].used==1){
				printf("%s=%s",names[cd[i].to].c_str(),names[cd[i].x].c_str());
				if(cd[i].tp==2)
					printf("%c%s",cd[i].op,names[cd[i].y].c_str());
				printf("\n");
			}
		if(names[redid[varl[redstr["res"]]]]!="res"&&init[redid[varl[redstr["res"]]]])
			printf("res=%s",names[redid[varl[redstr["res"]]]].c_str());
		return 0;
	}
}
int main(){
	estidi::main();
	return 0;
}
posted @ 2025-07-17 20:10  123asdf123  阅读(22)  评论(0)    收藏  举报