整体来说这次的题不是很难,我都能自己想出来,并写完,但是这次马力有点大,C题写了2hrs…
这题比较简单,重点在一个 xor的性质,与位的异或次序无关即两数 A , B A,B A,B 共 K K K 位
⋀ i A i ∧ B i = ( ⋀ i A i ) ∧ ( ⋀ i B i ) \bigwedge_i A_i \wedge B_i = (\bigwedge_i A_i)\wedge (\bigwedge_i B_i) i⋀Ai∧Bi=(i⋀Ai)∧(i⋀Bi)
code: github
tips 1: M > N M>N M>N 是无效的,因为仅有最后被访问的才会被记录,所以如果对于 领地 k k k 它访问了两次,那仅仅需要知道最后一次访问的时间就行了
tips 2: 因为仅有最后一次被访问才会被记录,即时间戳越后的优先于时间戳越早的,因此我们可以倒过来模拟整个过程,即从 G i G_i Gi 项最后一次待的地方开始往前模拟。
即有如下伪代码:
vis[] // 第 i 个东西最后被访问的时间戳
g[i] // cur,当前位置,dir行动方向,ans,访问次数,
for time = M to 0:
if(vis[g[i].cur] <=time) // 还未被访问过
vis[g[i].cur] = time
g[i].ans ++
update g[i].cur
接下来就是优化模拟的过程了,
tips 3: 任意的项 G i G_i Gi 仅由当前位置 c u r cur cur ,和行进方向 d i r dir dir 决定它的ans,因此可以合并所有 c u r cur cur 和 d i r dir dir 相同的项到一组。
tips 4: 可以用一个 访问的位置数目 n o _ v i s no\_vis no_vis 来记录还有多少位置未访问,到了 0就退出.
前4项技巧已经能够通过test 1和test 2 了,
这是我原始的代码
#include
using namespace std;
#define ms(x,v) memset(x,(v), sizeof(x))
#define msn(x,v,n) memset(x,(v),sizeof(x[0]) * n)
#define INF 0x3f3f3f3f
typedef long long LL;
typedef pair<int,int> PII;
const int MAXN = 2e5 +10;
struct Node{
int cur,dir,ans;
vector<int> ids;
};
void solve(){
int N,G,M;
cin >> N >> G>>M;
vector<vector<int> > guests(2*N);
for(int i=0 ; i< G; ++i){
int s;
char x;
cin >> s>>x;
s--;// map to [0,N)
int dir = x == 'C' ? 1 : -1;
s = (s + dir * (M%N) +N) % N;
int idx = (s<<1) + (dir==1);
guests[idx].push_back(i);
}
vector<Node> gg;
for(int i=0 ; i<guests.size() ; ++i){
if(guests[i].empty())continue;
Node tmp = {
i>>1,
i&1 ? -1 : 1, // anti-dir
0,
guests[i],
};
gg.push_back(tmp);
}
vector<int> timep(N,-1);
int not_vis = N;
if(M > N) M = N;
for(int time = M ; time >= 0 && not_vis>0 ; --time)
{
for(int i=0 ; i< gg.size() ; ++i){
int & cur = gg[i].cur;
if(timep[cur]<time){
not_vis--;
timep[cur] = time;
}//first access
if(timep[cur] <=time){
gg[i].ans ++;
}
gg[i].cur= (cur + gg[i].dir + N) % N;
}
}
vector<int> ans(G);
for(auto e: gg){
for(auto id : e.ids)
ans[id] = e.ans;
}
for(int i=0 ; i < ans.size(); ++i)
cout << " " << ans[i];
}
int main(int argc, char const *argv[])
{
ios :: sync_with_stdio(0);
cin.tie(0);
// cout.tie(0);
std::cout.precision(8);
std::cout.setf( std::ios::fixed, std:: ios::floatfield );
int T;
cin >> T;
for(int kase =1; kase <= T ; ++kase)
{
cout << "Case #"<<kase << ":";
solve();
cout << '\n';
}
return 0;
}
(其实是可以被hack的…,最坏情况下仍旧需要 O ( M G ) O(M G) O(MG)) 不过估计这个比较随机
tips 5: 延续tips 3的想法,任意一项它的访问路径仅仅由 c u r , d i r cur,dir cur,dir 决定,因此如果某个状态 c u r , d i r cur,dir cur,dir 已经出现过了,那么这一项就不会被记录了。用个array记录一个这个值,由于 d i r dir dir 仅仅有 2 2 2
因此,状态最多 2 N 2N 2N
所以复杂度 O ( 2 N l o g ( N ) ) O(2N log(N)) O(2Nlog(N)) (实际中我用了set来记录还能更新的集合)
code: github
这题也比较简单,只是编码有点复杂
tips 1: 按 X i X_i Xi 的大小给这 n n n 个点排序,如果固定warehouse 的位置 i i i, 那么选取 X j > X i X_j > X_i Xj>Xi 的点的花费是 X j − X i + C j X_j - X_i + C_j Xj−Xi+Cj,选取 X j < X i X_j < X_i Xj<Xi 的花费是 X i − X j + C j X_i - X_j + C_j Xi−Xj+Cj, 因此对于这 n n n 个点,任意固定 warehouse 的位置后,计算上面的值,选前 k k k 个求和就是答案。
伪代码:
for warehouse = 0 to N:
for j = 0 to N:
计算每个点的花费
选取前 $k$ 个求和
这是 ok的只是复杂度太大了, O ( N 2 ) O(N^2) O(N2),我们可以优化一下
如果我们维护两个集合对于 X j > X i X_j > X_i Xj>Xi 计算 upper_set(X_j+C_j), 对于 X i > X j X_i > X_j Xi>Xj 的 计算 lower_set(-X_j+C_j) , 有了这两个集合后我们也能计算出答案。同时想象一下 warehouse 从i -> i+1 的过程,显然,第 i i i 项会从 upper_set 移到 lower,同时还需要维护一个第 k k k 项的分界点(这个贡献意义上) 的位置,简单想一下,维护这个不可能每次都经过 O ( N ) O(N) O(N) 次,虽然我不会算它的复杂度,但在平均意义上我觉得仅仅会移动 O(1) 次,因此复杂度是 O ( N l o g N ) O(N logN) O(NlogN) 左右一定能通过
code : github
版权声明
本作品为作者原创文章,采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
作者: taotao
转载请保留此版权声明,并注明出处