文章目录
PREV275:双向排序
双向排序
题意:
给定序列
(
a
1
,
a
2
,
…
,
a
n
)
=
(
1
,
2
,
…
,
n
)
(a_1,a_2,\dots,a_n)=(1,2,\dots,n)
(a1,a2,…,an)=(1,2,…,n),即
a
i
=
i
a_i = i
ai=i。对这个序列进行m次操作。一共两种操作:
- op = 0,pos = i:将 a 1 , a 2 , … , a i a_1,a_2,\dots,a_i a1,a2,…,ai降序排列;
- op = 1,pos = i:将
a
i
,
…
,
a
n
−
1
,
a
n
a_i,\dots,a_{n-1},a_n
ai,…,an−1,an升序排列。
求完成操作后的序列
思路:
用(op,pos)来简写表示操作op和pos的选择,用[x,pos]表示数x的位置在pos上。
对于数n的位置,如果存在(0,n)并且该操作之后不再有(1,1),那么一定[n,1]成立。因为n是最大的,(0,n)排序之后n必然在第一位,所有的(1,?)都无法动摇数n,而(1,?)中只有(1,1)能碰到位置1,但是它也不再有,所以[n,1]一定成立。反之,如果(1,1)的出现位置在(0,n)之后,那么[n,n]一定成立。理由同理。如果这两个都没有出现,可以默认初始状态是(1,1)。并且可以发现其他数字在(0,n)(1,1)之前的操作都没有意义,因为这两个操作会重排所有数据。
接下来判断n-1的位置,会发现正常情况下n位置确定后n-1是粘着n的,因为无论升序降序n-1都是和n连着的。那么可以得到n判断结束后的n-1的所在位置。
假设还空着的位置的范围是[zuo,you],由于n位置已经定了,n-1是剩下的数里面最大的值,那么n-1的最终值要么在zuo要么在you。
假设n定完后n-1在zuo,那么(0,?)已经无法影响到n-1的位置了,只需要判断(1,zuo)是否在后续中出现了,如果出现了[n-1,you]必定成立,反之[n-1,zuo]必定成立。定完n-1的位置之后,n-2的初始位置也定了,更新下zuo、you的值。然后同理推完所有数值的位置
//但是很难受了想了很久,正式比赛的时候想这么久还不如暴力多做几道题,泪目了
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mod = 1e9+7;
const ll mx = 1e5 + 10;
const ll inf = 1<<30;
ll sit[2][mx];
ll a[mx], n, m;
ll ans[mx], zuo, you;
void dfs(ll x, ll curr, ll mystart){
if(x == 0) return;
//x是当前的值 curr是当前值的位置
if(x == n){//特判
mystart = max(sit[0][n], sit[1][1]);
if(sit[0][n]>sit[1][1]){//降序排序
ans[1] = x;
zuo ++;
dfs(x-1,2, mystart);
}
else{//保持升序 - 包括两者都不出现的时候
ans[n] = x;
you --;
dfs(x-1,n-1, mystart);
}
return;
}
//目前的未固定范围[zuo,you]
if(curr == zuo){
//影响到位置变换的操作是 sit[1][zuo]
if(sit[1][zuo]>mystart){//就会影响
ans[you] = x;
you --;
dfs(x-1, you, sit[1][zuo]);
}
else{//不会影响 保持原状
ans[zuo] = x;
zuo ++;
dfs(x-1, zuo, mystart);
}
}
else{//curr == you
//影响到位置变换的操作是 sit[0][you]
if(sit[0][you]>mystart){
ans[zuo] = x;
zuo ++;
dfs(x-1, zuo, sit[0][you]);
}
else{
ans[you] = x;
you --;
dfs(x-1, you, mystart);
}
}
}
void solve(){
scanf("%lld %lld", &n, &m);
for(ll i = 1; i <= m; i++){
ll op, pos;
scanf("%lld %lld", &op, &pos);
sit[op][pos] = i;
}
zuo = 1, you = n;
dfs(n, n, 0);
for(ll i = 1; i <= n; i++){
printf("%lld", ans[i]);
if(i==n) puts("");
else printf(" ");
}
}
int main(){
solve();
return 0;
}
PREV270:括号序列
括号序列
题意:
给定一个括号序列,要求添加最少的括号数使合法。求有多少种本质不同的添加结果。本质不同指最后完整的括号序列至少有一个位置上的字符不同。
思路:
首先,从左至右肯定是先添加完 右括号 再添加 左括号。否则会出现()
这样的无意义的添加,是一种浪费,不可能是最少的括号。
所以肯定可以把括号序列划分为两段,左边一段是添加右括号的,右边一段是添加左括号的。(或者只存在一段)。
因此可以把括号和下标值压栈,配对出栈。这样最后栈里留下的字符一定是)))……(((
,找到最右边的)和最左边的(,这两个字符就是划分左右区段的标志。
接着,考虑到求左区段方案数和右区段方案数其实是等价的(把右区段翻转然后符号替换)。所以可以只考虑求左区段的方案数求法。最后左右区段的方案数乘起来取模得到答案。
对于()))
类似的的左区段(右括号的数量多于左括号),可以从右到左dp。
对于从左至右第i个右括号,dp[j]表示从当前右括号的左邻到括号序列的右末放进去j个左括号的方案数。
同时记录cv,cv表示当前右括号左邻最多能放下几个左括号。
易得
d
p
[
i
]
=
d
p
′
[
i
]
+
d
p
′
[
i
−
1
]
+
⋯
+
d
p
′
[
i
−
c
v
]
dp[i] = dp'[i]+dp'[i-1]+\cdots+dp'[i-cv]
dp[i]=dp′[i]+dp′[i−1]+⋯+dp′[i−cv]
这样的复杂度是O(n),但是发现式子右边是连续和,可以再记录一个前缀和sum数组,这样复杂度就变成了O(1)。
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 5e3 + 10;
const ll mod = 1e9 + 7;
const ll inf = 1000000000 + 10;
char str[mx];
ll dp[mx], sum[mx];
ll f(char *s, ll len){//[0,len-1] (())))())
memset(dp, 0, sizeof dp);
memset(sum, 0, sizeof sum);
ll cv = 0, need = 0;//最多放几个(
rep(i,0,len-1){
if(s[i]=='(')need--;
else need++;
}
dp[0] = 1;
rep(i,0,need)sum[i] = 1;
vector<ll>li;
ll i = len-1;
while(i>=0){
if(s[i]==')')cv++;
else cv--;
if(i==0 || s[i-1]==')'){
li.pb(cv);
}
i--;
}
rep(i,0,li.size()-1){
cv = li[i];
rep(i,0,cv){
if(i-cv-1<0) dp[i] = sum[i];
else dp[i] = sum[i] - sum[i-cv-1];
dp[i] = (dp[i]%mod+mod)%mod;
}
rep(i,cv+1,need)dp[i] = 0;
rep(i,1,need) {
sum[i] = sum[i-1]+dp[i];
sum[i]%=mod;
}
}
return dp[need];
}
char ff(char c){
return c==')'?'(':')';
}
void solve(){
// scanf("%s", str);
ll len = strlen(str);
stack<pll>sta;
rep(i,0,len-1){
if(sta.empty())sta.push(mp(str[i],i));
else{
if(sta.top().fi==str[i]){
sta.push(mp(str[i], i));
}
else{
if(str[i]==')')sta.pop();
else sta.push(mp(str[i],i));
}
}
}
if(sta.size()<=1){
printf("%lld\n", (ll)sta.size());
return;
}
ll you = -1, zuo = len;//[0,you] [you+1,len-1]
while(!sta.empty() && sta.top().fi=='('){
zuo = sta.top().se;
sta.pop();
}
if(!sta.empty())you = sta.top().se;
ll ans1 = 1, ans2 = 1;
if(you!=-1){
ans1 = f(str,you+1);
}
if(zuo!=len){
ll l = zuo, r=len-1;
while(l<=r){
swap(str[l],str[r]);
str[l] = ff(str[l]);
if(l!=r) str[r] = ff(str[r]);
l++,r--;
}
ans2 = f(str+zuo,(len-1)-(zuo)+1);
}
printf("%lld\n", ans1*ans2%mod);
}
int main(){
// cin.tie(0);
// ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
while(scanf("%s",str)!=EOF)
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV268:左孩子右兄弟
左孩子右兄弟
题意:
把一个多叉树转化为左孩子右兄弟,可以选择任意孩子作为左孩子。使树深度最大。
思路:
设dp[i]表示节点i所在子树的最大深度,容易发现,把dp[son]最大的孩子节点放在最下面更优。则转移方程为:
d
p
[
i
]
=
m
a
x
{
d
p
[
s
o
n
]
}
+
s
o
n
S
i
z
e
dp[i] = max\{dp[son]\}+sonSize
dp[i]=max{dp[son]}+sonSize
这里我用的深度从1开始。所以最后答案要-1。
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 1000000000 + 10;
ll N;
vector<ll>e[mx];
ll dfs(ll rt){
ll sonSize = e[rt].size();
if(sonSize ==0LL)return 1LL;
ll maxv = 0;
for(ll i =0; i< sonSize;i++){
ll son = e[rt][i];
maxv = max(maxv, dfs(son));
}
return maxv + sonSize;
}
void solve(){
scanf("%lld", &N);
rep(i,2,N){
ll fa;
scanf("%lld", &fa);
e[fa].pb(i);
}
ll ans = dfs(1);
printf("%lld\n", ans-1LL);
}
int main(){
// cin.tie(0);
// ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV266:异或数列
异或数列
题意:
给定长度为n的数列x。Alice和Bob初始值为0,两个人都可以做以下两个操作之一:
- Alice的值异或上 X i X_i Xi
- Bob的值异或上
X
i
X_i
Xi
i是自己选定的。但是每个i只能选一次。最后拥有较大数的一方赢,两者相等平局。Alice先手。
先手赢1,平局0,输-1。
思路:
转化为二进制值进行考虑。记录2的i次幂的数量,记为num[i]。
显然,当num[i]为偶数的时候它对于结果没有影响,因为无论这个值给谁,要么偶数次异或为0,要么两者都奇数次异或值相同。
而num[i]为奇数的时候,它一定带来Alice和Bob直接的差距。并且num[i]为奇数的最大的i一定决定了结果,因为其他的小于i的2的次幂的差距之和最大也不可能超过i。
接着简单考虑奇数次个2的次幂的影响。假设次幂为1。
1个1的时候先手赢,3个1的时候先手也赢(先手加给Bob,使Bob进行偶数次异或),5个1的时候先手也赢……
实际上,1一个1的时候确保了先手有异或后手没有,所以赢。
而其他情况,是确保了最后一步由先手来做。那么先手可以根据情况加给自己或者对方,使得对方小于自己。
再来考虑除了奇数次个2的次幂之外存在其他数的情况,由上面可以知道,最后一个处理奇数次幂值的人决定了胜负。所以如果总数n为奇数,先手决定胜负。(特判num[i]为1的情况,因为先手可以确保直接抢占。)
综上,如果num[i]为1,先手胜。如果num[i]全为偶数,平局。如果num[i]奇数且n为奇数先手胜,反之先手败。
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 1000000000 + 10;
ll T, x[mx];
ll num[30];
void solve(){
scanf("%lld", &T);
rep(t, 1, T){
memset(num, 0, sizeof num);
ll n;
scanf("%lld", &n);
rep(i,1,n){
scanf("%lld", &x[i]);
rep(j,0,20){
if(x[i]&1) num[j]++;
x[i]>>=1;
}
}
ll win = 0;
irep(i,20,0){
if(num[i]&1){
if(num[i]==1) win = 1;
else{
if(n&1) win = 1;
else win = -1;
}
break;
}
}
printf("%lld\n", win);
}
}
int main(){
// cin.tie(0);
// ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV262:砝码称重
砝码称重
题意:
N个砝码,第i个砝码重wi。求一共可以称多少种重量。
思路:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示前i个砝码是否能构成状态j,状态j表示左边的重量减去右边的重量。由于可能是负数,可以用
d
p
[
i
]
[
b
a
s
e
+
j
]
dp[i][base+j]
dp[i][base+j]表示
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
//typedef long long ll;
typedef int ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 2e5 + 10;
const ll base = 1e5 + 5;
const ll total = 1e5;
ll N, w[110];
bool dp[110][mx];
void solve(){
cin>>N;
rep(i,1,N)cin>>w[i];
dp[0][base] = true;
rep(i,1,N){
rep(j,base - total, base + total){
dp[i][j] = dp[i-1][j];
ll bef1 = j - w[i], bef2 = j + w[i];
if(bef1 >= base-total && bef1 <= base + total)
dp[i][j] |= dp[i-1][bef1];
if(bef2 >= base-total && bef2 <= base + total)
dp[i][j] |= dp[i-1][bef2];
}
}
ll ans = 0;
rep(j, base + 1, base+total) ans += dp[N][j];
cout<<ans<<endl;
}
int main(){
cin.tie(0);
ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV258:重复字符串
重复字符串
题意:
如果一个字符串S可以由某个字符串重复K次得到,则S为K次重复字符串。给定字符串S,求至少修改几个字符可以使其变为K次字符串。
思路:
字符串S变为K次字符串,就是表示变为K个相同的子串。每个子串里有Y=|S|/|K|个字符。那么可以把题目拆解为Y个长为K的字符数组,求当前数组内字符全部相同的最小修改数,然后求和Y个数组的修改总值。
按照题意,当S不为K的倍数的时候应该返回-1。但是这么做始终只有90分。后来我发现(阴差阳错?)不返回-1然后按照普通求数组修改数能拿到100分。但我不知道为什么而且觉得很没有道理。。感觉是碰巧凑到了答案。
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
ll K, nn[30];
char s[mx], t[mx];
ll num(char*t, ll gs){
ll ans = 0;
memset(nn, 0, sizeof nn);
rep(i,0,gs-1){
nn[t[i]-'a']++;
ans = max(ans, nn[t[i]-'a']);
}
return gs-ans;
}
void solve(){
scanf("%lld", &K);
scanf("%s", s);
ll len = strlen(s);
if(K>len){
printf("-1\n");
return;
}
if(len == K){
printf("%lld\n", num(s, len));
return;
}
ll ans = 0, group = len/K;
rep(lp,0,group-1){
ll tot = 0;
for(ll i = lp, j = 0; i<len; i+=group, j++){
t[j] = s[i];
}
ans += num(t, K);
}
printf("%lld\n", ans);
}
int main(){
// cin.tie(0);
// ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV256:天干地支
天干地支
题意:
模拟天干地支变化
思路:
模拟。但是wa了两次。。。还是要细心稳妥(喷血)
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e5 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
string s1[20], s2[20];
void init1(){
s1[0] = "jia";
s1[1] = "yi";
s1[2] = "bing";
s1[3] = "ding";
s1[4] = "wu";
s1[5] = "ji";
s1[6] = "geng";
s1[7] = "xin";
s1[8] = "ren";
s1[9] = "gui";
}
void init2(){
s2[0] = "zi";
s2[1] = "chou";
s2[2] = "yin";
s2[3] = "mao";
s2[4] = "chen";
s2[5] = "si";
s2[6] = "wu";
s2[7] = "wei";
s2[8] = "shen";
s2[9] = "you";
s2[10] = "xu";
s2[11] = "hai";
}
ll cy =0, c1 = 6, c2 = 8;
void add(){
c1++;
if(c1 == 10)c1=0;
c2++;
if(c2 == 12)c2=0;
}
void solve(){
init1();
init2();
ll year;
cin>>year;
while(cy!=year){
add();
cy++;
}
cout<<s1[c1]<<s2[c2]<<endl;
}
int main(){
cin.tie(0);
ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV248:补给
补给
题意:
改进后的TSP问题。每个点经过至少一次,但是可以多次。
思路:
先floyd求出两点间的最小合法距离(大于D),然后TSP。
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 20 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
const ll mx2 = 2e6 + 10;
double d[mx][mx], D;
double x[mx], y[mx];
double dp[mx][mx2];
bool is0(double x){
return abs(x-0.0)<eps;
}
ll n;
double pf(double x){return x*x;}
double dis(ll i, ll j){
return sqrt(pf(x[i]-x[j])+pf(y[i]-y[j]));
}
void solve(){
scanf("%lld %lf", &n, &D);
rep(i,0,n-1)scanf("%lf %lf", &x[i], &y[i]);
rep(i,0,n-1){
rep(j,0,n-1){
d[i][j] = dis(i,j);
if(d[i][j]>D)d[i][j] = -1;
}
}
rep(k,0,n-1){
rep(i,0,n-1){
rep(j,0,n-1){
if(d[i][k]<0 || d[k][j]<0)continue;
double nv = d[i][k]+d[k][j];
if(d[i][j]<0)d[i][j] = nv;
d[i][j] = min(d[i][j], nv);
}
}
}
// rep(i,0,n-1){
// rep(j,0,n-1)printf("%.2lf ", d[i][j]);
// puts("");
// }
rep(V,1,(1<<n)-1){
rep(j,0,n-1){
bool has = (V>>j)&1;
if(!has)continue;
ll resV = V - (1<<j);
if(resV == 0){
dp[j][V] = d[j][0];
continue;
}
rep(k,0,n-1){
bool hasK = (V>>k)&1;
if(!hasK || k == j)continue;
double nv = d[j][k]+dp[k][resV];
if(is0(dp[j][V])){
dp[j][V] = nv;
}
dp[j][V] = min(dp[j][V], nv);
}
}
}
printf("%.2lf\n", dp[0][(1<<n)-1]);
}
int main(){
cin.tie(0);
ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV246:画廊
画廊
题意:
L幅作品在左边,R幅在右边。从画廊起点正中心出发,到画廊终点正中心结束。
思路:
d
p
[
i
]
[
j
]
[
0
]
dp[i][j][0]
dp[i][j][0]表示已经解决了左边i幅右边j幅,并且停在左边的走过的距离。
d
p
[
i
]
[
j
]
[
1
]
dp[i][j][1]
dp[i][j][1]表示已经解决了左边i幅右边j幅,并且停在右边的走过的距离。
函数
d
i
s
t
(
d
o
u
b
l
e
a
,
d
o
u
b
l
e
b
,
b
o
o
l
s
a
m
e
)
dist(double a, double b, bool same)
dist(doublea,doubleb,boolsame)求两点之间距离,a和b是和画廊起点的距离,same表示这两点是否在同一边。
则dp转移方程:
d
p
[
i
]
[
j
]
[
0
]
=
m
i
n
{
d
p
[
i
−
1
]
[
j
]
[
0
]
+
d
i
s
t
(
l
[
i
−
1
]
,
l
[
i
]
,
1
)
,
d
p
[
i
−
1
]
[
j
]
[
1
]
+
d
i
s
t
(
r
[
j
]
,
l
[
i
]
,
0
)
}
dp[i][j][0] = min\{ dp[i-1][j][0]+dist(l[i-1],l[i],1), dp[i-1][j][1]+dist(r[j],l[i],0) \}
dp[i][j][0]=min{dp[i−1][j][0]+dist(l[i−1],l[i],1),dp[i−1][j][1]+dist(r[j],l[i],0)}
d
p
[
i
]
[
j
]
[
1
]
dp[i][j][1]
dp[i][j][1]的转移方程同理。最后加上到达终点的距离。
//第一遍写的时候样例和自测样例都过了 结果0分。。。。太粗心了 自测样例写的也不好,这么丢分真的好窒息
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 5e2 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
const ll mx2 = 2e6 + 10;
double dp[mx][mx][2];//当前在:0-left, 1-right
double l[mx], r[mx];
ll L, R, d, w;
double pf(double a){
return a*a;
}
double dist(double a, double b, bool same){
if(same) return sqrt(pf(a-b));
return sqrt(pf(a-b)+pf(w));
}
void solve(){
scanf("%lld %lld %lld %lld", &L, &R, &d, &w);
rep(i,1,L)scanf("%lf", &l[i]);
rep(i,1,R)scanf("%lf", &r[i]);
dp[1][0][0] = sqrt(pf(w/2.0)+pf(l[1]));
dp[0][1][1] = sqrt(pf(w/2.0)+pf(r[1]));
rep(i,2,L) dp[i][0][0]=dp[i-1][0][0]+dist(l[i],l[i-1],1);
rep(i,2,R) dp[0][i][1]=dp[0][i-1][1]+dist(r[i],r[i-1],1);
rep(i,1,L){
rep(j,1,R){
double nv1, nv2;
nv1 = dp[i-1][j][0]+dist(l[i-1],l[i],1);
nv2 = dp[i-1][j][1]+dist(r[j],l[i],0);
dp[i][j][0]=(i==1?nv2:min(nv1,nv2));
nv1 = dp[i][j-1][1]+dist(r[j-1],r[j],1);
nv2 = dp[i][j-1][0]+dist(l[i],r[j],0);
dp[i][j][1]=(j==1?nv2:min(nv1,nv2));
}
}
double nv1 = dp[L][R][0]+sqrt(pf(w/2.0)+pf(d-l[L]));
double nv2 = dp[L][R][1]+sqrt(pf(w/2.0)+pf(d-r[R]));
printf("%.2lf\n", min(nv1, nv2));
}
int main(){
cin.tie(0);
ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV244:游园安排
游园安排
题意:
最长上升子序列板子题。但是复杂度要求
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
思路:
d
p
[
s
u
b
]
dp[sub]
dp[sub]表示的是,目前求得的上升子序列长度为sub时的最小字符。
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 1e6 + 10;
const ll mod = 1e9 + 7;
const ll inf = 100000 + 10;
struct node{
ll len, sx;
ll zfc;
char name[12];
}nos[mx];
ll id, dp[mx], nex[mx], len;
char input[mx];
ll check(node a, node b){//-1 a<b; 1 a>b
return strcmp(a.name,b.name);
}
void solve(){
scanf("%s", input);
ll num = strlen(input), i = 0;
while(i<num){
id++;
nos[id].name[0] = input[i];
nos[id].sx = i;
ll j = i+1, t = 1;
while(j<num && input[j]>='a'&&input[j]<='z'){
nos[id].name[t++]=input[j];
j++;
}
nos[id].name[t] = '\0';
i = j;
}
dp[++len] = 1;
rep(i,2,id){
ll zuo = 1, you = len, mid;
while(zuo <= you){
mid = (zuo+you)/2;
ll nid = dp[mid];
if(check(nos[nid], nos[i])>=0LL){
//nid 大于等于自己i
you = mid -1;
}
else{
zuo = mid + 1;
}
}
//zuo = 结果
if(zuo > len){
dp[++len] = i;
nex[i] = dp[len-1];
}
else{
dp[zuo] = i;
nex[i] = dp[zuo-1];
}
}
ll x = dp[len];
stack<ll>v;
while(x!=0){
v.push(x);
x = nex[x];
}
while(!v.empty()){
printf("%s", nos[v.top()].name);
v.pop();
}
puts("");
}
int main(){
// cin.tie(0);
// ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}
PREV241:奇偶覆盖
奇偶覆盖
题意:
n个矩形求奇数次覆盖的面积和偶数次覆盖的面积。
思路:
离散化+两次扫描线。第一次求总面积,第二次求奇数覆盖面积。
求总面积的时候,线段树节点里的cover表示当前区间整体覆盖的次数。pushup的时候取min。pushdown的时候加上flag。
求奇数覆盖面积的时候,线段树节点里的cover表示当前区间奇数覆盖的长度。pushup的时候取和。pushdown的时候根据flag判断奇偶性质变化。
之所以求奇数不求偶数的原因是,奇数确保了肯定存在矩形,偶数当cover为0的时候不能确保当前有矩形。
//谁写谁吐血之丑陋的代码
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define pll pair<ll,ll>
#define rep(i,l,r) for(ll i = l; i <= r; i++)
#define irep(i,r,l) for(ll i = r; i >= l; i--)
//#define LOCAL
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double PI=acos(-1.0);
const ll mx = 2e6 + 10;
const ll inf = 100000 + 10;
struct Line{//从下往上扫描 化边为点
ll x1, x2;//实际的边为[x1,x2-1]
bool add;//是矩形入边吗
};
ll n, l1[mx], r1[mx], l2[mx], r2[mx];
ll lenx, leny;
vector<ll>vl, vr;
map<ll,ll>lid, rid;
vector<Line>e[mx];
struct node{//线段树
ll zuo, you;
ll cover, flag;
}nos[mx<<1];
ll getval(ll c){
//nos[c].you+1 - nos[c].zuo
return vl[nos[c].you] - vl[nos[c].zuo-1];
}
void init(ll c, ll l, ll r){
nos[c].zuo = l, nos[c].you = r;
nos[c].cover = nos[c].flag = 0LL;
if(l == r){
nos[c].cover = nos[c].flag = 0LL;
return;
}
ll mid = (l+r)>>1;
init(c<<1,l,mid);
init(c<<1|1,mid+1,r);
}
void pushup(ll c){
nos[c].cover = min(nos[c<<1].cover, nos[c<<1|1].cover);
}
void pushdown(ll c){
ll f = nos[c].flag;
nos[c].flag = 0;
nos[c<<1].cover += f, nos[c<<1].flag += f;
nos[c<<1|1].cover += f, nos[c<<1|1].flag += f;
}
void change(ll c, ll l, ll r, ll L, ll R, bool add){
if(L<=l && r<=R){
if(add) nos[c].cover++, nos[c].flag++;
else nos[c].cover--, nos[c].flag--;
return;
}
pushdown(c);
ll mid = (l+r)>>1;
if(L<=mid) change(c<<1,l,mid,L,R,add);
if(R>mid) change(c<<1|1,mid+1,r,L,R,add);
pushup(c);
}
ll query(ll c, ll l, ll r, ll L, ll R){
if(l == r || nos[c].cover){
return nos[c].cover?getval(c):0LL;
}
pushdown(c);
ll mid = (l+r)>>1;
ll ans = 0;
ans += query(c<<1,l,mid,L,R);
ans += query(c<<1|1,mid+1,r,L,R);
pushup(c);
return ans;
}
ll scanLine(){//扫描线算法
init(1,1,lenx);
ll ansS = 0, dy = -1;
rep(i,1,leny){//从下往上扫描
ll num = e[i].size();
if(num == 0LL)continue;
//求面积
ll cx = query(1,1,lenx,1,lenx);
ll ddy = vr[i-1]-dy;
if(dy!=-1LL)ansS += cx * ddy;
//更新
rep(j,0,num-1){
Line cl = e[i][j];
change(1,1,lenx,cl.x1,cl.x2,cl.add);
}
dy = vr[i-1];
}
return ansS;
}
void pushup2(ll c){
nos[c].cover = nos[c<<1].cover + nos[c<<1|1].cover;
}
void pushdown2(ll c){
ll f = nos[c].flag;
nos[c].flag = 0;
if(f){
nos[c<<1].cover = getval(c<<1) - nos[c<<1].cover;
nos[c<<1|1].cover = getval(c<<1|1) - nos[c<<1|1].cover;
}
nos[c<<1].flag ^= f;
nos[c<<1|1].flag ^= f;
}
void change2(ll c, ll l, ll r, ll L, ll R){
if(L<=l && r<=R){
nos[c].cover = getval(c) - nos[c].cover;
nos[c].flag = 1 - nos[c].flag;
return;
}
pushdown2(c);
ll mid = (l+r)>>1;
if(L<=mid) change2(c<<1,l,mid,L,R);
if(R>mid) change2(c<<1|1,mid+1,r,L,R);
pushup2(c);
}
ll scanLine2(){
init(1,1,lenx);
ll ansS = 0, dy = -1;
rep(i,1,leny){//从下往上扫描
ll num = e[i].size();
if(num == 0LL)continue;
//求面积
ll ddy = vr[i-1]-dy;
if(dy!=-1LL)ansS += nos[1].cover * ddy;
//更新
rep(j,0,num-1){
Line cl = e[i][j];
change2(1,1,lenx,cl.x1,cl.x2);
}
dy = vr[i-1];
}
return ansS;
}
void solve(){
scanf("%lld", &n);
rep(i,1,n){
scanf("%lld %lld %lld %lld",&l1[i], &r1[i], &l2[i], &r2[i]);
vl.pb(l1[i]);vl.pb(l2[i]);
vr.pb(r1[i]);vr.pb(r2[i]);
}
sort(all(vl));
sort(all(vr));
vl.erase(unique(all(vl)),vl.end());
vr.erase(unique(all(vr)),vr.end());
lenx = vl.size(), leny = vr.size();
rep(i,0,lenx-1) lid[vl[i]] = i+1;
rep(i,0,leny-1) rid[vr[i]] = i+1;
//离散化数据
rep(i,1,n){
l1[i] = lid[l1[i]], l2[i] = lid[l2[i]];
r1[i] = rid[r1[i]], r2[i] = rid[r2[i]];
}
//数据处理完毕 开始扫描线
//预处理 矩形入边出边的数据
rep(i,1,n){//从下往上扫描 l1l2为x坐标 r1r2为y坐标
if(r1[i]>r2[i]) swap(r1[i],r2[i]);
if(l1[i]>l2[i]) swap(l1[i],l2[i]);
if(l1[i]==l2[i])continue;//面积为0
Line ru,chu;
ru.add = true, chu.add = false;
ru.x1 = l1[i], ru.x2 = l2[i]-1;
chu.x1 = l1[i], chu.x2 = l2[i]-1;
e[r1[i]].pb(ru);
e[r2[i]].pb(chu);
}
//扫描线
lenx --;//去掉了最大的不会处理的值
ll totalS = scanLine();
ll jiS = scanLine2();
printf("%lld\n%lld\n", jiS, totalS - jiS);
}
int main(){
// cin.tie(0);
// ios::sync_with_stdio(0);
#ifdef LOCAL
freopen("1.txt", "r", stdin);
#endif
solve();
#ifdef LOCAL
fclose(stdin);
#endif
return 0;
}