传送门
法一:遇到这个绝对值,很容易想到找零点,我于是就打了80分的暴力,但细节没处理好,就直接爆了5分;实际上,想到找零点后可以很自然地过渡到零点分段,先预处理每一个零点,再排序,二分找出即可。
法二:将|ax+b|这个函数图像画出,可知它是一个下凸函数,而下凸函数的和仍然是下凸函数,则可以用三分做。
代码都很类似,下面贴的是二分:
#include
#define ll long long
#define db double
#define re register
#define cs const
#define N 300005
using namespace std;
inline int read()
{
int x=0,f=1;
char ch;
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
db w[N];
int n,a[N],b[N],l,r;
db work(int x)
{
db ans=0;
for(int i=1;i<=n;i++)
ans+=abs(a[i]*w[x]+b[i]);
return ans;
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
b[i]=read();
w[i]=-(db)b[i]/a[i];
}
sort(w+1,w+1+n);
l=1,r=n;
while(l+2<r)
{
int l1=(l+r)/2;
int r1=(l+r)/2+1;
if(work(l1)<work(r1)) r=r1;
else l=l1;
}
db sum=1e18;
for(int i=l;i<=r;i++) sum=min(sum,work(i));
printf("%lf",sum);
return 0;
}
传送门
30pts暴力:
询问时搜索一遍数列即可,复杂度为(n*m);
60pts暴力:
考虑没有C操作的情况,预处理每个1-maxh的岛屿个数,加上前面的30,期望得分60pts
100pts正解:
我们可以用线段树来处理,线段树下标为高度,值为岛屿个数,后面即可单点查询,区间修改,如果a[i] < a[i+1],那么在一个数据结构内,我们将a[i]+1到a[i+1]区间加1,因为其可能出现相等情况,故加一.
树状数组也可以做,但我认为线段树好理解点。
代码:
#include
#define db double
#define re register
#define cs const
#define N 600005
using namespace std;
inline int read()
{
int x=0,f=1;
char ch;
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
int a,b,h[N],tree[N*4],n,m,last;
char s[100];
void add(int k,int l,int r,int ll,int rr,int val)
{
if(l>=ll&&r<=rr) tree[k]+=val;
else
{
int mid=(l+r)/2;
if(ll<=mid) add(k<<1,l,mid,ll,rr,val);
if(mid<rr) add(k<<1|1,mid+1,r,ll,rr,val);
}
}
int query(int k,int l,int r,int pos)
{
int ans=0;
ans+=tree[k];
if(l==r) return ans;
int mid=(l+r)/2;
if(pos<=mid) ans+=query(k<<1,l,mid,pos);
else ans+=query(k<<1|1,mid+1,r,pos);
return ans;
}
int main()
{
n=read();
m=read();
h[0]=-1;
for(int i=1;i<=n;i++) h[i]=read();
for(int i=1;i<=n;i++) if(h[i-1]<h[i]) add(1,0,N,h[i-1]+1,h[i],1);
if(n<=5000&&m<=5000)
{
while(m--)
{
scanf("%s",s);
if(s[0]=='Q')
{
a=read();
a=a^last;
int ans=0;
for(int i=1;i<=n;i++) if(h[i]>=a&&h[i-1]<a) ans++;
printf("%d\n",ans);
last=ans;
}
else
{
a=read();
b=read();
h[a^last]=b^last;
}
}
}
else
{
while(m--)
{
scanf("%s",s);
if(s[0]=='Q')
{
a=read();
a=a^last;
a=query(1,0,N,a);
printf("%d\n",a);
last=a;
}
else
{
a=read();
b=read();
a=a^last;
b=b^last;
if(h[a-1]<h[a]) add(1,0,N,h[a-1]+1,h[a],-1);
if(h[a]<h[a+1]&&a<n) add(1,0,N,h[a]+1,h[a+1],-1);
h[a]=b;
if(h[a-1]<h[a]) add(1,0,N,h[a-1]+1,h[a],1);
if(h[a]<h[a+1]&&a<n) add(1,0,N,h[a]+1,h[a+1],1);
}
}
}
}
树状数组:
#include
using namespace std;
const int MX = 500005;
struct FEN
{
int sum[MX];
void add(int p, int x)
{
for(int i=p+1; i; i-=i&-i) sum[i] += x;
}
int qur(int p)
{
int ret = 0;
for(int i=p+1; i<MX; i+=i&-i) ret += sum[i];
return ret;
}
} F;
template <typename T> void read(T &x)
{
x = 0; char c = getchar(); bool f = 0;
while(!isdigit(c) && c!='-') c = getchar();
if(c == '-') f = 1, c = getchar();
while(isdigit(c)) x = x*10+c-'0', c = getchar();
if(f) x = -x;
}
int n, m;
int h[MX];
void input()
{
read(n), read(m);
for(int i=1; i<=n; i++) read(h[i]);
}
void work()
{
int lans = 0;
for(int i=1; i<=n; i++)
if(h[i-1] < h[i])
F.add(h[i], +1), F.add(h[i-1], -1);
for(int i=1; i<=m; i++)
{
int a, b;
char c = getchar();
while(!isalpha(c)) c = getchar();
if(c == 'Q')
{
read(a);
a ^= lans;
printf("%d\n", lans = F.qur(a));
}
else
{
read(a), read(b);
a ^= lans, b ^= lans;
if(h[a-1] < h[a]) F.add(h[a], -1), F.add(h[a-1], +1);
if(h[a] < h[a+1]) F.add(h[a+1], -1), F.add(h[a], +1);
h[a] = b;
if(h[a-1] < h[a]) F.add(h[a], +1), F.add(h[a-1], -1);
if(h[a] < h[a+1]) F.add(h[a+1], +1), F.add(h[a], -1);
}
}
}
int main()
{
freopen("patrick.in","r",stdin);
freopen("patrick.out","w",stdout);
input();
work();
return 0;
}
传送门
这道TMD是他们noi的T1.
题意可转化为:给定一张n点m边无向图,边有权值,权值仅能为1或2(对于图上每个点,所有与其相连的边权值总和为奇数),要求给定一个给边定向的方案,使得对于每个点而言,入度和出度相差不超过1
20pts暴力:
暴力枚举每一条边。
40pts暴力;
考虑点数非常少的情况,对于两个点x;y,若连接他们的边中有多条权值相同的边,可以每两条分一组,方向互异消去这两条边。如此以来图中便剩下最多(n-1)n条边,枚举这些边即可。
100pts正解:
详见fsy和wyh巨佬的blog,我是真的菜鸡,虽然写了注释,但还是理解不到 。
fsy
wyh
代码:
#include
template <typename _tp> inline void read(_tp&x){
char ch=getchar(),ob=0;x=0;
while(ch!='-'&&!isdigit(ch))ch=getchar();if(ch=='-')ob=1,ch=getchar();
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();if(ob)x=-x;
}
const int N = 1001000, M = N + N;
int dir[2][N];//这条边对于我来说,是加贡献,还是减贡献(相当于钦定了方向)
//根据题意,我们规定0表示这个点得到贡献,1表示这个点减去贡献。
int rev[M], ans[M], depend[M];//depend存这个点删边依赖的边的编号
int vis[N];//rev 数组 解决的问题是 在最后的时候 ans[i] 由其 depend的边转换过来时 是否需要取反
int n, m, tot;
struct node {
int id, to;//id:与该点相连的边的编号。
}a[2][N];//to:这条边对面的点号。
// a表示所有的 权值为t的边
void add(int x, int y, int id, int t) {//x,y表示连接的点 id表示正在加入第i条边 t表示权值
if(a[t][x].id&&a[t][x].to == y) {//如果x,y曾经有边,就会产生需要删除的情况。
depend[a[t][x].id] = depend[id] = 0;//当前正在加入的边和之前该点对应的边的依靠为0
ans[id] = 1, ans[a[t][x].id] = dir[t][y];//当前边ans 设为 1 之前边 ans设为 y的上一条边的方向
a[t][x].id = a[t][y].id = 0;//把他们的id 设为 0 相当于删边
return ;
}
bool flg = true;
if(a[t][x].id) {//如果 x 之前有边
flg = false;
depend[a[t][x].id] = tot;// 那么x之前的边 依靠于新边
rev[a[t][x].id] = dir[t][x];//rev 对于x来说 他之前边的方向 与 其指向相同
//而在实际计算中后面对ans 的计算中 其实是取反了的
//因为对于q[x].id这条边来说 他与新加入的边的关系 相同的
//若 d==1 则相当于之前的边是指向 x的
//若 新边的方向为 0 那么 这一条边的方向不变
//否则 会改变
//建议手玩一下
a[t][x].id = 0;//把x 之前的边 删了
x = a[t][x].to;//将x指向 之前边指向的另一个点
}
if(a[t][y].id) {//与 x同理
flg = false;
depend[a[t][y].id] = tot;
rev[a[t][y].id] = dir[t][y] ^ 1;//这里与x刚好相反
a[t][y].id = 0;
y = a[t][y].to;
}
a[t][x].to = y, a[t][y].to = x;//互相 成为 对方到达的点
dir[t][x] = 1, dir[t][y] = 0;//钦定方向 默认 从 x -> y的 方向
if(flg) a[t][x].id = a[t][y].id = id;//他们的 id 相当于 当前加入的边
else depend[id] = a[t][x].id = a[t][y].id = tot++;//将他们的id设为新点并且将他们的依靠设为新边
//至于关于depend的疑惑 实际在 32 和 45行就已经处理过了
//相当于如果 flag == 0就先当于加边之后有环了所以可以将xy对应的之前的边以及当前加入的边依靠在新边上
}
void work(int p) {
int x = p, t = 0;
vis[x] = 1;
//t是权值 x是点 a[t][x]相当于 x点在权值t下 对应的唯一一条边
//这里相当于从边权为1的边开始走
while(a[t][x].id) {
ans[a[t][x].id] = dir[t][x];//先 将当前边的ans暂且的设为该边本身的方向
if(vis[a[t][x].to]) return ;//如果出现环了 就返回 因为继续走下去 他还是个环。
// 不用担心ans没更新完 因为主函数里面 是一个for 循环
vis[a[t][x].to] = true;
x = a[t][x].to, t ^= 1;
}
//这里相当于从边权为2的边开始走
x = p, t = 1;
while(a[t][x].id) {
ans[a[t][x].id] = dir[t][x] ^ 1;
if(vis[a[t][x].to])return;
vis[a[t][x].to] = true;
x = a[t][x].to, t ^= 1;
}
}
int main() {
read(n), read(m), tot = m+1;
for(int i=1,x,y,z;i<=m;++i) {
read(x), read(y), read(z);
add(x, y, i, z-1);//第i条边 对应c-1的权值 连接的是a,b
}
for(int i=0;i<n;++i)
if(!vis[i]) work(i);
for(int i=tot;i;--i)
if(depend[i]) ans[i] = ans[depend[i]] ^ rev[i];//rev 如果为1 就要取反 否则 就不取反
for(int i=1;i<=m;++i)
putchar('0'+ans[i]);
putchar(10);
return 0;
}