CSPS-2023

密码锁(lock)

考场想推一个复杂度牛逼的东西,后来发现直接 \(O(10^5)\) 枚举状态,\(O(40)\) 判断合不合法就行了。并且我考场降智了,我乘上了一个 \(O(2^8)\) 枚举每个状态推到这八种密码是用哪种操作,但其实可以不用判断的,因为我们只关心行不行,不关心是用的哪种操作。但是因为我加了一些减枝所以 \(O(10^5 \times 2^8 \times 40)\) 跑的还挺快。

code:

#include<bits/stdc++.h>
//#define int long long
#define ll long long
#define re register
#define il inline

using namespace std;

int n,ans;
int p[10],c[10],a[10][10];

il int read()
{
	int f = 0 , s = 0;
	char ch = getchar();
	for(;!isdigit(ch);ch=getchar()) f |= (ch=='-');
	for(; isdigit(ch);ch=getchar()) s = (s<<1)+ (s<<3) + (ch-'0');
	return f ? -s : s;
}

il void check()
{
	int cnt = 0 , pos1 = 0 , pos2 = 0;
	for(re int i=1;i<=n;i++)
	{
		if(c[i] == 1)
		{
			cnt = 0;
			for(re int j=1;j<=5;j++) if(a[i][j] != p[j]) cnt++;
			if(cnt > 1) return ;
		}
		if(c[i] == 2)
		{
			cnt = pos1 = pos2 = 0;
			for(re int j=1;j<=5;j++)
			{
				if(a[i][j] != p[j])
				{
					cnt++;
					if(cnt >= 3) return ;
					if(!pos1) pos1 = j;
					else pos2 = j;
				}
			}
			if(pos1+1 != pos2) return ;
			if(((a[i][pos1]-p[pos1]+10) % 10) != ((a[i][pos2]-p[pos2]+10) % 10)) return ;
		}
	}
	ans++;
}

il bool judge(int i)
{
	int cnt = 0;
	for(re int j=1;j<=5;j++) if(a[i][j] != p[j]) cnt++;
	if(cnt > 1) return 0;
	else return 1;
}

il void dfs2(int id)
{
	if(id == n+1)
	{
		check();
		return ;  
	}
	if(judge(id))
	{
		c[id] = 1 , dfs2(id+1);
		c[id] = 2 , dfs2(id+1);
	}
	else c[id] = 2 , dfs2(id+1);
}

il void dfs1(int id)
{
	if(id == 6)
	{
		bool flag = 0;
		for(re int i=1;i<=n;i++)
		{
			flag = 0;
			for(re int j=1;j<=5;j++)
				if(a[i][j] != p[j]) { flag = 1; break; }
			if(!flag) return ;
		}
		dfs2(1);
		return ;
	}
	for(re int i=0;i<=9;i++)
	{
		p[id] = i;
		dfs1(id+1); 
	}
}

signed main()
{
	//freopen("lock.in","r",stdin);
	//freopen("lock.out","w",stdout);
	n = read();
	for(re int i=1;i<=n;i++)
		for(re int j=1;j<=5;j++) 
			a[i][j] = read();
	if(n == 1) { cout << 81; return 0; }
	dfs1(1);
	cout << ans;
	return 0;
}

消消乐(game)

首先容易想到一个 \(O(n^2)\) 算法:我们枚举左端点,然后从左端点开始往右扫,每次扫的时候开一个栈,如果当前栈顶和要加进来的字母是同一个字母,就把栈顶 pop 掉,否则将这个字母入栈。如果在某一时刻栈是空的,这说明从左端点到这个时刻的这一段区间就是可消的,我们让答案++。

我们考虑怎么优化这个过程,如果在某一时刻栈是空的,这也就说明,在此时,栈顶的状态和在左端点时的状态是一样的。于是,我们就可以只需要从 \(1\) 开始从左往右扫一遍,每次加入一个新的字母时,判断它之前有多少个时候的状态跟它是一样的,这个我们可以通过 hash 来实现这个状态的存储,然后用一个桶来存储每个状态的数目,这么做即可做到近乎线性。因为 unordered_map 不被卡的话是 \(O(1)\) 的,所以可以通过 \(2\times 10^6\)

code:

#include<bits/stdc++.h>
#define int unsigned long long
#define ull unsigned long long
#define ll long long
#define re register
#define il inline
const int N = 2e6 + 5;
const int base = 191;
using namespace std;

int n,top,ans;
ull Hash[N];
char ch[N];
struct STACK{
	char ch;
	int id;
}stk[N];
unordered_map <ull,int> t;

il int read()
{
	int f = 0 , s = 0;
	char ch = getchar();
	for(;!isdigit(ch);ch=getchar()) f |= (ch=='-');
	for(; isdigit(ch);ch=getchar()) s = (s<<1)+ (s<<3) + (ch-'0');
	return f ? -s : s;
}

signed main()
{
	n = read();
	cin >> (ch+1);
	t[0] = 1;
	for(re int i=1;i<=n;i++)
	{
		if(!top)
		{
			stk[++top] = {ch[i],i};
			Hash[i] = ch[i] - '0';
			ans += t[Hash[i]] , t[Hash[i]]++;
		}
		else
		{
			if(stk[top].ch == ch[i])
			{
				top--;
				Hash[i] = Hash[stk[top].id];
				ans += t[Hash[i]] , t[Hash[i]]++;
			}
			else
			{
				Hash[i] = Hash[stk[top].id] * base + (ch[i] - '0');
				stk[++top] = {ch[i],i};
				ans += t[Hash[i]] , t[Hash[i]]++;
			}
		}
		//cout << Hash[i] << " ";
	}
	cout << ans;
	return 0;
}

结构体(struct)

神必模拟,不想多说啥,抄的我叠 MichaelWong 的代码,大模拟做不了一点,里面有我大叠给我的一些注释。

#include<bits/stdc++.h>
#define int long long
#define ll long long
#define next nxt
#define re register
#define il inline
#define pii pair <int,int>
#define pis pair <ll,string>
const int N = 150 + 5;
using namespace std;
int max(int x,int y){return x > y ? x : y;}
int min(int x,int y){return x < y ? x : y;}

int n,opt,varcnt=3; //varcnt 动态记录变量类型总数
int totaddr,size[N],alisz[N];//内存占用情况,不同类型的大小和他们的对齐要求
vector <pis> varlist; //存地址的结束点(不包含这个点)和变量名
map <string,int> varid; //通过类型名获取类型编号 (id)
map <string,pii> address; // 通过名字获取地址起始点(包含这个点)和他的类型 id
// 一个 type 型变量 name,他占据了 [l,r) 的内存空间,则 varist 存的 r,address 存了 l
struct structure{
	vector <pis> varlist;
	map <string,pii> address;
}str[N];

il int read()
{
	int f=0,s=0;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f |= (ch=='-');
	for(; isdigit(ch);ch=getchar()) s = (s<<1) + (s<<3) + (ch^48);
	return f ? -s : s;
}

il void declare_struct()
{
	string name,type;
	int addr = 0,nxt,k,tid;
	cin >> type >> k;
	varid[type] = ++varcnt;
	str[varcnt].varlist.resize(k);
	for(re int i=0;i<k;i++)
	{
		cin >> type >> name;
		tid = varid[type];
		if(addr % alisz[tid]) nxt = addr / alisz[tid] * alisz[tid] + alisz[tid];
		else nxt = addr;
		str[varcnt].address[name] = {nxt,tid};
		str[varcnt].varlist[i] = {addr=nxt+size[tid],name};
		alisz[varcnt] = max(alisz[varcnt],alisz[tid]);
	}
	if(addr % alisz[varcnt]) size[varcnt] = addr / alisz[varcnt] * alisz[varcnt] + alisz[varcnt];
	else size[varcnt] = addr;
	cout << size[varcnt] << " " << alisz[varcnt] << "\n";
	return ;
}

il void declare_variable()
{
	string name,type;
	cin >> type >> name;
	int tid = varid[type];
	ll nxt;
	if(totaddr % alisz[tid]) nxt = totaddr / alisz[tid] * alisz[tid] + alisz[tid];
	else nxt = totaddr;
	address[name] = {nxt,tid};
	varlist.push_back({totaddr=nxt+size[tid],name});
	cout << nxt << "\n";
	return ;
}

il void visit_element()
{
	string s,now;
	cin >> s , s = " " + s;
	int len=s.length()-1,pos=0,nxt=len+1,tid,nxtid;//pos是已找到的上一个 ".",nxt 是最新找到的 "."
	int addr = 0;
	for(re int i=pos+1;i<=len;i++) if(s[i] == '.') { nxt = i; break ; }
	now = s.substr(pos+1,nxt-pos-1);
	auto pr = address[now];//找到地址起始点和类型 id
	tid = pr.second , addr += pr.first , pos = nxt , nxt = len + 1;
	while(pos != len+1)//进入结构体内部
	{
		for(re int i=pos+1;i<=len;i++) if(s[i] == '.') { nxt = i; break ; }
		now = s.substr(pos+1,nxt-pos-1);
		pr = str[tid].address[now];
		nxtid = pr.second , addr += pr.first , pos = nxt , nxt = len + 1;
		tid = nxtid;
	}
	cout << addr << "\n";
	return ;
}

il void visit_address()
{
	int addr = 0;
	int tid,nxtid;
	string s="",now;
	cin >> addr;
	if(varlist.empty()) { puts("ERR"); return ; }
	if(addr >= (*--varlist.end()).first) { puts("ERR"); return ; }
	auto pr = *upper_bound(varlist.begin(),varlist.end(),pis{addr,"zzzzzzzzzz"});
	now = pr.second , tid = address[now].second , addr -= pr.first-size[tid] , s += now;
	if(addr < 0) { puts("ERR"); return ; }
	while(tid > 3)
	{
		s += '.';
		if(addr >= (*--str[tid].varlist.end()).first) { puts("ERR"); return ; }
		pr = *upper_bound(str[tid].varlist.begin(),str[tid].varlist.end(),pis{addr,"zzzzzzzzzz"});
		now = pr.second , nxtid = str[tid].address[now].second , addr -= pr.first-size[nxtid] , s += now;
		tid = nxtid;
		if(addr < 0) { puts("ERR"); return ; }
	}
	cout << s << "\n";
	return ; 
}

signed main()
{
	n = read();
	varid["byte"] = 0 , varid["short"] = 1 , varid["int"] = 2 , varid["long"] = 3;
	for(re int i=0;i<=3;i++) size[i] = alisz[i] = (1<<i);
	for(re int i=1;i<=n;i++)
	{
		opt = read();
		if(opt == 1) declare_struct();
		if(opt == 2) declare_variable();
		if(opt == 3) visit_element();
		if(opt == 4) visit_address();
	}
	return 0;
}

种树(tree)

首先这个答案肯定是具有单调性的,所以我们可以想到二分。它问的是最少需要多少天完成任务,那我们就二分这个结束时间。

然后就是怎么分配哪天种哪个地方的树了。首先我们可以等比数列求和公式等东西 + 分类讨论求出当结束时间为 \(x\) 时,第 \(i\) 号地块最晚在第几天种上树,可以在结束时间前长到需求高度,我们记为 \(day_i\)

但是这时我们想一下,这个 \(day_i\) 的定义其实是不那么好的,我们考虑,如果 \(x\) 的孩子 \(y\)\(day_y\)\(\ge day_x\),那么为了 \(y\)
能种上树,\(x\) 的最晚起始时间是需要提前的,也就是 \(\displaystyle day_x = \min(day_x,\min_{y\in son(x)}\{day_y-1\})\),一遍 dfs 求出即可。

我们贪心地考虑,显然,对于 \(day_i\) 较大的地块,我们可以让他往后稍稍,让 \(day_i\) 较小的种上树,这样才能最大化的满足要求。我们可以通过维护一个小根堆来实现这个过程。最后如果说种树成功了,那就说明这个结束时间可以,然后根据结果修改一下 \(l,r\) 即可,时间复杂度 \(O(n\log^2n)\)

有如下细节需要注意:

  • 因为题目中保证 \(ans \leq 10^9\),所以我们的二分边界只需要设到 \(10^9\) 而不是再往大了走,因为我的代码里二分里又套了个二分,所以这个边界不同,常数会有大不同。最初做的时候没有看到这个保证,我开的 \(r = 10^{14}\),喜提 TLE \(70\)pts。

  • 在使用等比数列求和公式的时候,可能会出现爆 long long 的情况,这时候就有一个小妙招 by xwh_Marvelous:如果爆 long long,那肯定就是这个变量值变负数了,因为题目中 \(a_i \leq 10^9\),所以爆 long long 肯定说明这个最晚的开始时间是合法的,判一下是否是负数就行了。

#include<bits/stdc++.h>
#define int long long
#define ll long long
#define re register
#define il inline
const int N = 1e5 + 5;
using namespace std;

int n,u,v;
ll sum,fir,sec;
ll h[N],b[N],c[N],Late[N];
bitset <N> vis;
struct node{
	ll day,id;
	bool operator <(const node &t) const {
		return day > t.day; 
	}
};
vector <int> G[N];

il int read()
{
	int f = 0 , s = 0;
	char ch = getchar();
	for(;!isdigit(ch);ch=getchar()) f |= (ch=='-');
	for(; isdigit(ch);ch=getchar()) s = (s<<1)+ (s<<3) + (ch-'0');
	return f ? -s : s;
}//O(n!) can't get 20pts,but O(nlog^2n) can get 100pts

il bool GetSum(int i,int day,int lastday)
{
	if(b[i] + day * c[i] >= 1)
	{
		if(c[i] == 0)
		{
			sum = b[i] * (lastday-day+1);
			if(sum < 0) sum = 1e18;
			if(sum >= h[i]) return true;
			else return false;
		}
		if(c[i] > 0)
		{
			fir = b[i] + day * c[i] , sec = b[i] + lastday * c[i];
			sum = (fir+sec)*(lastday-day+1)/2;
			if(sum < 0) sum = 1e18;
			if(sum >= h[i]) return true;
			else return false;
		}
		if(c[i] < 0)
		{
			int midday = min((b[i]-1)/(-c[i]),lastday);
			fir = b[i] + day * c[i] , sec = b[i] + midday * c[i];
			sum = (fir+sec)*(midday-day+1)/2 + (lastday-midday);
			if(sum >= h[i]) return true;
			else return false;
		}
	}
	else
	{
		if(lastday-day+1 >= h[i]) return true;
		else return false;
	}
	return true;
}

il int GetAns(int i,int lastday)
{
	int l = 1 , r = lastday , ans = 0;
	while(l <= r)
	{
		int mid = (l+r) >> 1;
		if(GetSum(i,mid,lastday)) ans = mid , l = mid + 1;
		else r = mid - 1;
	}
	return ans;
}

il void dfs(int x,int fa)
{
	for(re auto y : G[x])
	{
		if(y == fa) continue;
		dfs(y,x);
		Late[x] = min(Late[x],Late[y]-1);
	}
}

il bool check(int lastday)
{
	priority_queue <node> q;
	vis.reset();
	for(re int i=1;i<=n;i++)
	{
		Late[i] = GetAns(i,lastday);
		if(!Late[i]) return false;
	}
	dfs(1,0);
	for(re int i=1;i<=n;i++) if(!Late[i]) return false;
	vis[1] = 1;
	for(re auto x : G[1]) q.push({Late[x],x});
	for(re int i=2;i<=n;i++)
	{
		if(q.empty()) return false;
		int x = q.top().id; q.pop();
		if(Late[x] < i) return false;
		vis[x] = 1;
		for(re auto y : G[x]) if(!vis[y]) q.push({Late[y],y});
	}
	return true;
}

signed main()
{
	n = read();
	for(re int i=1;i<=n;i++) h[i] = read() , b[i] = read() , c[i] = read();
	for(re int i=1;i<n;i++)
	{
		u = read() , v = read();
		G[u].push_back(v) , G[v].push_back(u);
	}
	int l = 1 , r = 1e9 , ans = 0;
	while(l <= r)
	{
		int mid = (l+r) >> 1;
		if(check(mid)) ans = mid , r = mid - 1;
		else l = mid + 1;
	}
	cout << ans;
	return 0;
}
posted @ 2023-10-28 15:07  Bloodstalk  阅读(190)  评论(2编辑  收藏  举报