T1[转化枚举角度]给出长度是n的a序列,和长度是m的b序列,给出Q组询问\([p,q]\),求\(\sum_{p*i+q*j=C}^{}ai*bj\)。(n,m,C,q<=3e5)
考场
上来想枚举,对于\(max(p,q)>=\sqrt{C}\),直接暴力枚举就好了,但是\(max(p,q)<\sqrt{C}?\),没思路,算了,看数据,发现好多0啊,而且不是0的数大量重复,于是记忆化一下询问就直接A了
正解
其实做法没错,但是要分析一下为什么对于\(max(p,q)<\sqrt{C}\)离线处理暴力算是对的。
\(time=\sum_{p \epsilon [1,\sqrt{C}]} \sum_{q\epsilon[p,\sqrt{C}]}\frac{C}{q}\),发现后面的东西接近C,所以等效成\(\sqrt{C}*\sum q\),复杂度就是对的。
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define chu printf
#define rint register int
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 3e5 + 100;
const int mod = 998244353;
int n, m, T, C;
int a[N], b[N];
map<pair<int, int>, int> mp; //记录所有询问,统计计算答案
int id, ans[N];
struct node {
int p, q, bl;
} que[N];
inline int gcd(int x, int y) {
if (!y)
return x;
return gcd(y, x % y);
}
int main() {
// freopen("sedge3.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("sedge.in", "r", stdin);
freopen("sedge.out", "w", stdout);
n = re(), m = re(), C = re();
for (rint i = 1; i <= n; ++i) a[i] = re();
for (rint i = 1; i <= m; ++i) b[i] = re();
ans[0] = 0;
T = re();
for (rint i = 1; i <= T; ++i) {
que[i].p = re(), que[i].q = re();
int gd = gcd(que[i].p, que[i].q);
if (C % gd) //无法凑出,没有正整数解
{
que[i].bl = 0;
continue;
}
if (1ll * que[i].p * n + 1ll * que[i].q * m < C) {
que[i].bl = 0;
continue;
}
if (mp.find(make_pair(que[i].p, que[i].q)) == mp.end()) {
que[i].bl = id + 1;
++id;
mp[make_pair(que[i].p, que[i].q)] = id;
} //还有exgcd优化,看情况加不加!!!
else
que[i].bl = mp[make_pair(que[i].p, que[i].q)];
}
for (pair<pair<int, int>, int> Ele : mp) //遍历元素,暴力求解
{
pair<int, int> ele = Ele.first;
if (ele.first > ele.second) {
int sum = 0;
int bj = C / ele.first; // bj是fir最多用
bj = min(bj, n);
for (rint i = 0; i <= bj; ++i) {
int res = C - i * ele.first;
if (res % ele.second || (res / ele.second > m))
continue;
sum = (1ll * sum + 1ll * a[i] * b[res / ele.second] % mod) % mod;
}
ans[Ele.second] = sum;
} else {
int sum = 0;
int bj = C / ele.second; // bj是fir最多用
bj = min(bj, m);
for (rint i = 0; i <= bj; ++i) {
int res = C - i * ele.second;
if (res % ele.first || (res / ele.first > n))
continue;
sum = (1ll * sum + 1ll * a[res / ele.first] * b[i] % mod) % mod;
}
ans[Ele.second] = sum;
}
}
for (rint i = 1; i <= T; ++i) chu("%d\n", ans[que[i].bl]);
return 0;
}
/*
3 4 5
1 2 3
1 2 3 4
2
1 1
1 2
*/
T2[SOS DP]给出m种商店和n个物品,每种物品在不同商店都有不同价值,如果在某个商店买了东西就要付出额外bi的代价,在第j个商店买i物品代价是a[i][j].求买全所有物品的最小花费。(n<=1e5,m<=25)
考场
这不就无脑DP?一算\(O(n*2^m*m)\),想想本质,其实枚举商店后决策就确定了,但是复杂度不减少...
于是对于<=1e8的复杂度点直接暴力
剩下的贪心:
先忽略b进行a的选择,然后考虑把商店按照b从大到小排序,依次删除商店,购买的物品选择当前最优解去买(这其实是假的),
发现第3个数据过不了,然后把b排序后的商店小的那一半随机打乱,然后它过了。
然后它A了
神奇...
正解
用到子集DP,其实就是快速计算出选择某商店集合,对每个物品的已选商店最小价值加和。
\(f[S]表示对于不能够选择S中的物品的最小花费\)
\(g[S]:先预处理出针对每个物品把商店按照价格sort依次加入到S集合中的花费,对于i物品,新加入j商店的代价就是\)
\(a_{i,j+1}-a_{i,j},表示j不能作为选择,那么下一个最优可选集合就是第一个大于它的,差值就是这个\)
\(但是这样只是计算出对于每个商店价格连续的从小到大集合,比如a1<a2<a3<a4,那么1000,1100,1110,1111被累加\)
\(但是1101,1001没有计算,这时就需要f[i]=\sum g[s],s\epsilon i\),子集和,针对每个物品累加有用的只是其中包含的从min开始连续段(否则一定选择最小断点),差分的值代表含义就是累加后就是最小未选物品的价值。
\(比如1101,会被1100,1000,0000子集累加,刚好是第3小的价值,0001虽然有但是因为不含min所以价值0\)
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define chu printf
#define rint register int
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 1e5 + 100, M = 27;
int a[N][M], b[M], n, m, bit[M], buc[M], bl[N], p[M];
vector<int> g[M];
inline bool cmp(int x, int y) { return b[x] > b[y]; }
inline void solve_slow() {
int mx = (1 << m) - 1;
ll ans = 1e18;
for (rint s = 1; s <= mx; ++s) // s是选择的AI集合
{
ll cost = 0;
bit[0] = 0;
for (rint j = 1; j <= m; ++j) {
if (s & (1 << (j - 1)))
cost += b[j], bit[++bit[0]] = j;
}
for (rint i = 1; i <= n; ++i) {
int minn = 2e9;
for (rint J = 1, j = bit[J]; J <= bit[0]; J++, j = bit[J]) {
minn = min(minn, a[i][j]);
}
cost += minn;
}
ans = min(ans, cost);
}
chu("%lld\n", ans);
}
int main() {
// freopen("putin1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("putin.in", "r", stdin);
freopen("putin.out", "w", stdout);
srand(time(0));
n = re(), m = re();
for (rint i = 1; i <= n; ++i)
for (rint j = 1; j <= m; ++j) a[i][j] = re();
for (rint i = 1; i <= m; ++i) b[i] = re();
if (1ll * n * (1ll << m) * m <= 1e8) //勉强跑过
{
solve_slow();
return 0;
}
ll his = 1e18;
ll ans = 0;
for (rint i = 1; i <= n; ++i) {
ll minn = 1e18;
int from = 0;
for (rint j = 1; j <= m; ++j) {
if (minn > a[i][j])
minn = a[i][j], from = j;
}
bl[i] = from;
g[from].push_back(i);
buc[from]++;
ans += minn;
if (buc[from] == 1)
ans += b[from];
// chu("after :%d ans is:%d(from;%d minn:%lld b[]:%lld)\n",i,ans,from,minn,b[from]);
}
his = min(his, ans);
// chu("his;%lld\n",his);
for (rint i = 1; i <= m; ++i) p[i] = i;
sort(p + 1, p + 1 + m, cmp);
for (rint I = 1; I < m; ++I) //从大到小删除集合中b大的数,表示尝试只选择[i+1,m]的b
{
int i = p[I];
// chu("buc[%d]:%d\n",i,buc[i]);
if (!buc[i])
continue; //没有就不用删除
// chu("del:%d\n",i);
for (rint ele : g[i]) {
// ele另谋高就
ll minn = 1e18;
int from = 0;
for (rint J = I + 1; J <= m; ++J) {
int j = p[J];
if (!buc[j]) {
if (minn > b[j] + a[ele][j])
minn = b[j] + a[ele][j], from = j;
} else {
if (minn > a[ele][j])
minn = a[ele][j], from = j;
}
}
bl[ele] = from;
g[from].push_back(ele);
buc[from]++;
ans = ans + minn - a[ele][i];
// chu("add:%lld\n",minn-a[ele][i]);
}
ans = ans - b[i];
his = min(ans, his);
buc[i] = 0;
g[i].clear();
// chu("ans:%lld\n",ans);
}
int mid = m / 2;
random_shuffle(p + mid + 1, p + m + 1);
// for(rint i=1;i<=m;++i)chu("f:%d\n",p[i]);
// chu("shuffle:%d--%d\n",1+mid,m);
for (rint i = 1; i <= m; ++i) g[i].clear(), buc[i] = 0; // chu("p[%d]:%d\n",i,p[i]);
for (rint i = 1; i <= n; ++i) bl[i] = 0;
ans = 0;
for (rint i = 1; i <= n; ++i) {
ll minn = 1e18;
int from = 0;
for (rint j = 1; j <= m; ++j) {
if (minn > a[i][j])
minn = a[i][j], from = j;
}
bl[i] = from;
g[from].push_back(i);
buc[from]++;
ans += minn;
if (buc[from] == 1)
ans += b[from];
// chu("after :%d ans is:%d(from;%d minn:%lld b[]:%lld)\n",i,ans,from,minn,b[from]);
}
his = min(his, ans);
for (rint I = 1; I < m; ++I) //从大到小删除集合中b大的数,表示尝试只选择[i+1,m]的b
{
int i = p[I];
// chu("buc[%d]:%d\n",i,buc[i]);
if (!buc[i])
continue; //没有就不用删除
// chu("del:%d\n",i);
for (rint ele : g[i]) {
// ele另谋高就
ll minn = 1e18;
int from = 0;
for (rint J = I + 1; J <= m; ++J) {
int j = p[J];
if (!buc[j]) {
if (minn > b[j] + a[ele][j])
minn = b[j] + a[ele][j], from = j;
} else {
if (minn > a[ele][j])
minn = a[ele][j], from = j;
}
}
bl[ele] = from;
g[from].push_back(ele);
buc[from]++;
ans = ans + minn - a[ele][i];
// chu("add:%lld\n",minn-a[ele][i]);
}
ans = ans - b[i];
his = min(ans, his);
buc[i] = 0;
g[i].clear();
// chu("ans:%lld\n",ans);
}
chu("%lld", his);
return 0;
}
/*
*/
\(O(m*2^m)\)
T4[贪心/数据结构维护]给出n个点,m条边的图,每条边边权范围[li,ri],求m条边的边权组成m的一个排列,而且前n-1条边是图的最小生成树的构造方案,要求字典序最小。(n,m<=3500)
考场
没时间了我要打暴力!
<=10的直接阶乘枚举边,l=r的只判断了值不重复,但是有可能不是最小生成树啊!所以5pts跑了
正解
经典贪心:n个数第i个数的值域范围是[li,ri],求给n个数赋值成1-n的排列的构造方案是否存在
按照1--n的值开始分配,每次找到未确定的数中li<=now,ri>=now的ri最小的,赋值成now
构建树后,考虑形成若干对限制条件形如\(wi>wj\),那么对于\(li=max(li,lj+1),rj=min(rj,ri-1)\),构造这样的方案判断合法
可以直接判断是否是最小生成树条件,可以set双指针维护\(O(n*logn)\)扫一遍序列判断无解
接下来,对边按照编号从小到大分配值,对于\(i边可以分配j值等价于\forall L<=j<=R,R>\sum L<=lk<rk<=R\)
于是对每个边分配线段树维护到目前的R,每个位置L的值累计,判断合法。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define chu printf
#define rint register int
inline ll re()
{
ll x=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=3500+100;
int n,m,p[N];
vector<int>req[N],buc[N];
int ans[N];
pair<int,int>ea[N];
int vis[N],del[N];
multiset<int>ms;
struct edge
{
int u,v,l,r,id;
}E[N];
struct Picture
{
struct node
{
int to,nxt,id;
}e[N<<1];
int tot,head[N],fa[N],pre[N],dep[N];
inline void add(int x,int y,int bl)
{
e[++tot]=(node){y,head[x],bl};head[x]=tot;
}
inline void dfs(int x)
{
dep[x]=dep[fa[x]]+1;
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==fa[x])continue;
fa[to]=x;
pre[to]=e[i].id;
dfs(to);
}
}
inline void lca(int x,int y,int bl)
{
if(dep[x]<dep[y])swap(x,y);
while(dep[x]!=dep[y])
{
req[bl].push_back(pre[x]);
x=fa[x];
}
while(x!=y)
{
req[bl].push_back(pre[x]);
req[bl].push_back(pre[y]);
x=fa[x];y=fa[y];
}
}
}dug;
struct Segment
{
#define lson (rt<<1)
#define rson (rt<<1|1)
#define mid ((l+r)>>1)
int maxi[N<<2],tag[N<<2];
inline void pushup(int rt)
{
maxi[rt]=max(maxi[lson],maxi[rson]);
}
inline void update(int rt,int ad)
{
maxi[rt]+=ad;tag[rt]+=ad;
}
inline void pushdown(int rt)
{
if(!tag[rt])return;
update(lson,tag[rt]);update(rson,tag[rt]);
tag[rt]=0;
}
inline void build(int rt,int l,int r)
{
tag[rt]=0;
if(l==r)
{
maxi[rt]=l-1;
return;
}
build(lson,l,mid);build(rson,mid+1,r);
pushup(rt);
}
inline void insert(int rt,int l,int r,int L,int R,int vl)
{
//chu("maxi[8]:%d(ins:%d--%d :%d)(%d-%d)\n",maxi[8],L,R,vl,l,r);
if(L<=l&&r<=R)
{
update(rt,vl);return;
}
pushdown(rt);
if(L<=mid)insert(lson,l,mid,L,R,vl);
if(R>mid)insert(rson,mid+1,r,L,R,vl);
pushup(rt);
}
inline int query(int rt,int l,int r,int L,int R,int std)
{
//chu("!!!!!max[%d]:%d std:%d(%d--%d)\n",rt,maxi[rt],std,l,r);
if(maxi[rt]<std)return -1;
if(l==r)
{
if(maxi[rt]<std)return -1;
return l;
}
pushdown(rt);
if(R<=mid)return query(lson,l,mid,L,R,std);
if(maxi[lson]>=std)return query(lson,l,mid,L,R,std);
return query(rson,mid+1,r,L,R,std);
}
}seg;
int main()
{
freopen("mst.in","r",stdin);
freopen("mst.out","w",stdout);
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),m=re();
for(rint i=1;i<=m;++i)E[i].u=re(),E[i].v=re(),E[i].l=re(),E[i].r=re(),E[i].id=i;
for(rint i=1;i<n;++i)
dug.add(E[i].u,E[i].v,E[i].id),dug.add(E[i].v,E[i].u,E[i].id);
dug.dfs(1);
for(rint i=n;i<=m;++i)dug.lca(E[i].u,E[i].v,E[i].id);
for(rint i=n;i<=m;++i)
for(rint ele:req[i])
{
// chu("for:%d smaller should be:%d\n",i,ele);
E[ele].r=min(E[ele].r,E[i].r-1);
E[i].l=max(E[i].l,E[ele].l+1);
buc[ele].push_back(i);
}
int ip=1;
for(rint i=1;i<=m;++i)ea[i]=make_pair(E[i].l,E[i].r);
sort(ea+1,ea+1+m);
for(rint i=1;i<=m;++i)
{
while(ip<=m&&ea[ip].first<=i)ms.insert(ea[ip].second),ip++;
while(!ms.empty()&&(*ms.begin())<i)ms.erase(ms.begin());
if(ms.empty())
{
chu("-1");return 0;
}
ms.erase(ms.begin());
}//删除最小的一种构造判断有解
for(rint i=1;i<=m;++i)p[i]=i;
sort(p+1,p+1+m,[&](const int & x,const int & y){return E[x].r<E[y].r;});
// for(rint k=1;k<=m;++k)chu("seq:(%d-%d)(%d)\n",E[p[k]].l,E[p[k]].r,p[k]);
for(rint i=1;i<=m;++i)
{
// printf("!!!!!!!!!!!!lim{%d}:%d--%d\n",i,E[i].l,E[i].r);
for(rint j=1;j<=m;++j)vis[j]=0;
int ip=1;
seg.build(1,1,m);
// chu("maxi[8]:%d\n",seg.maxi[8]);
for(rint j=1;j<=m;++j)
{
// chu("try:%d\n",j);
// for(rint k=ip;k<=m;++k)chu("seq:(%d-%d)(%d)\n",E[p[k]].l,E[p[k]].r,p[k]);
if(del[j])seg.insert(1,1,m,1,j,1);//chu("add:%d--%d 1\n",1,j);
while(ip<=m&&E[p[ip]].r<=j)
{
if(E[p[ip]].id>i)seg.insert(1,1,m,1,E[p[ip]].l,1);//chu("dfd add:%d--%d:1(seq:%d)\n",1,E[p[ip]].l,p[ip]);
++ip;
}
int pos=seg.query(1,1,m,1,j,j);
// chu("query:%d--%d:%d\n",1,j,pos);
if(pos!=-1)
vis[pos]++,vis[j+1]--;//chu("for R:%d the lim:%d--%d\n",j,pos,j);
}
for(rint j=1;j<=m;++j)vis[j]+=vis[j-1];
for(rint j=E[i].l;j<=E[i].r;++j)
{
if(!del[j]&&!vis[j])
{
ans[i]=j;break;
}
}
del[ans[i]]=1;
for(rint j:buc[i])E[j].l=max(E[j].l,ans[i]+1);//chu("chg:%d-->%d\n",j,ans[i]+1);
for(rint j=i+1;j<=m;++j)while(del[E[j].l])E[j].l++;//chu("dfchg:%d-->%d\n",j,E[j].l);
}
for(rint i=1;i<=m;++i)chu("%d ",ans[i]);
return 0;
}
/*
4 5
1 3 1 3
1 4 2 4
1 2 1 4
2 3 3 3
2 3 3 5
int t=x;
x=y;
y=
*/