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;
}